From 52fb89ff7326b3570e9c42a435081dab559f610b Mon Sep 17 00:00:00 2001 From: Kevin Date: Thu, 31 Mar 2016 11:42:57 +0800 Subject: [PATCH] implement taints and tolerations --- .generated_docs | 3 + CHANGELOG.md | 1 - contrib/completions/bash/kubectl | 91 +++ docs/man/man1/kubectl-taint.1 | 203 ++++++ docs/man/man1/kubectl.1 | 2 +- docs/user-guide/kubectl/kubectl.md | 3 +- docs/user-guide/kubectl/kubectl_taint.md | 113 ++++ docs/yaml/kubectl/kubectl.yaml | 1 + docs/yaml/kubectl/kubectl_taint.yaml | 127 ++++ pkg/api/deep_copy_generated.go | 17 + pkg/api/helpers.go | 74 ++- pkg/api/types.generated.go | 586 ++++++++++++++++++ pkg/api/types.go | 67 ++ pkg/api/v1/conversion_generated.go | 50 ++ pkg/api/v1/deep_copy_generated.go | 17 + pkg/api/v1/generated.pb.go | 405 ++++++++++++ pkg/api/v1/generated.proto | 36 ++ pkg/api/v1/types.generated.go | 586 ++++++++++++++++++ pkg/api/v1/types.go | 67 ++ pkg/api/v1/types_swagger_doc_generated.go | 23 + pkg/api/validation/validation.go | 122 +++- pkg/api/validation/validation_test.go | 385 ++++++++++-- pkg/kubectl/cmd/cmd.go | 1 + pkg/kubectl/cmd/taint.go | 397 ++++++++++++ pkg/kubectl/cmd/taint_test.go | 299 +++++++++ pkg/kubectl/describe.go | 40 ++ .../scheduler/algorithm/predicates/error.go | 1 + .../algorithm/predicates/predicates.go | 55 ++ .../algorithm/predicates/predicates_test.go | 283 +++++++++ .../algorithm/priorities/taint_toleration.go | 110 ++++ .../priorities/taint_toleration_test.go | 229 +++++++ .../algorithmprovider/defaults/defaults.go | 17 + test/e2e/kubectl.go | 34 + test/e2e/scheduler_predicates.go | 247 ++++++++ 34 files changed, 4623 insertions(+), 69 deletions(-) create mode 100644 docs/man/man1/kubectl-taint.1 create mode 100644 docs/user-guide/kubectl/kubectl_taint.md create mode 100644 docs/yaml/kubectl/kubectl_taint.yaml create mode 100644 pkg/kubectl/cmd/taint.go create mode 100644 pkg/kubectl/cmd/taint_test.go create mode 100644 plugin/pkg/scheduler/algorithm/priorities/taint_toleration.go create mode 100644 plugin/pkg/scheduler/algorithm/priorities/taint_toleration_test.go diff --git a/.generated_docs b/.generated_docs index 5a9c70fc215..680f36075a6 100644 --- a/.generated_docs +++ b/.generated_docs @@ -55,6 +55,7 @@ docs/man/man1/kubectl-run.1 docs/man/man1/kubectl-scale.1 docs/man/man1/kubectl-set.1 docs/man/man1/kubectl-stop.1 +docs/man/man1/kubectl-taint.1 docs/man/man1/kubectl-uncordon.1 docs/man/man1/kubectl-version.1 docs/man/man1/kubectl.1 @@ -108,6 +109,7 @@ docs/user-guide/kubectl/kubectl_rollout_undo.md docs/user-guide/kubectl/kubectl_run.md docs/user-guide/kubectl/kubectl_scale.md docs/user-guide/kubectl/kubectl_set.md +docs/user-guide/kubectl/kubectl_taint.md docs/user-guide/kubectl/kubectl_uncordon.md docs/user-guide/kubectl/kubectl_version.md docs/yaml/kubectl/kubectl.yaml @@ -142,5 +144,6 @@ docs/yaml/kubectl/kubectl_run.yaml docs/yaml/kubectl/kubectl_scale.yaml docs/yaml/kubectl/kubectl_set.yaml docs/yaml/kubectl/kubectl_stop.yaml +docs/yaml/kubectl/kubectl_taint.yaml docs/yaml/kubectl/kubectl_uncordon.yaml docs/yaml/kubectl/kubectl_version.yaml diff --git a/CHANGELOG.md b/CHANGELOG.md index fe74603e244..59f1e67d056 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,6 @@ - [Other notable changes](#other-notable-changes) - [v1.3.0-alpha.3](#v130-alpha3) - [Downloads](#downloads) -- [v1.3.0-alpha.3](#v130-alpha3) - [Changes since v1.3.0-alpha.2](#changes-since-v130-alpha2) - [Action Required](#action-required) - [Other notable changes](#other-notable-changes) diff --git a/contrib/completions/bash/kubectl b/contrib/completions/bash/kubectl index cbbff861852..3bd85301d20 100644 --- a/contrib/completions/bash/kubectl +++ b/contrib/completions/bash/kubectl @@ -2955,6 +2955,96 @@ _kubectl_annotate() noun_aliases+=("svc") } +_kubectl_taint() +{ + last_command="kubectl_taint" + commands=() + + flags=() + two_word_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--all") + flags+=("--include-extended-apis") + flags+=("--no-headers") + flags+=("--output=") + two_word_flags+=("-o") + flags+=("--output-version=") + flags+=("--overwrite") + flags+=("--schema-cache-dir=") + flags_with_completion+=("--schema-cache-dir") + flags_completion+=("_filedir") + flags+=("--selector=") + two_word_flags+=("-l") + flags+=("--show-all") + flags+=("-a") + flags+=("--show-labels") + flags+=("--sort-by=") + flags+=("--template=") + flags_with_completion+=("--template") + flags_completion+=("_filedir") + flags+=("--validate") + flags+=("--alsologtostderr") + flags+=("--api-version=") + flags+=("--as=") + flags+=("--certificate-authority=") + flags+=("--client-certificate=") + flags+=("--client-key=") + flags+=("--cluster=") + flags+=("--context=") + flags+=("--insecure-skip-tls-verify") + flags+=("--kubeconfig=") + flags+=("--log-backtrace-at=") + flags+=("--log-dir=") + flags+=("--log-flush-frequency=") + flags+=("--logtostderr") + flags+=("--match-server-version") + flags+=("--namespace=") + flags_with_completion+=("--namespace") + flags_completion+=("__kubectl_get_namespaces") + flags+=("--password=") + flags+=("--server=") + two_word_flags+=("-s") + flags+=("--stderrthreshold=") + flags+=("--token=") + flags+=("--user=") + flags+=("--username=") + flags+=("--v=") + flags+=("--vmodule=") + + must_have_one_flag=() + must_have_one_noun=() + must_have_one_noun+=("cluster") + must_have_one_noun+=("componentstatus") + must_have_one_noun+=("configmap") + must_have_one_noun+=("daemonset") + must_have_one_noun+=("deployment") + must_have_one_noun+=("endpoints") + must_have_one_noun+=("event") + must_have_one_noun+=("horizontalpodautoscaler") + must_have_one_noun+=("ingress") + must_have_one_noun+=("job") + must_have_one_noun+=("limitrange") + must_have_one_noun+=("namespace") + must_have_one_noun+=("node") + must_have_one_noun+=("persistentvolume") + must_have_one_noun+=("persistentvolumeclaim") + must_have_one_noun+=("petset") + must_have_one_noun+=("pod") + must_have_one_noun+=("podsecuritypolicy") + must_have_one_noun+=("podtemplate") + must_have_one_noun+=("replicaset") + must_have_one_noun+=("replicationcontroller") + must_have_one_noun+=("resourcequota") + must_have_one_noun+=("secret") + must_have_one_noun+=("service") + must_have_one_noun+=("serviceaccount") + must_have_one_noun+=("thirdpartyresource") + must_have_one_noun+=("thirdpartyresourcedata") + noun_aliases=() +} + _kubectl_config_view() { last_command="kubectl_config_view" @@ -3648,6 +3738,7 @@ _kubectl() commands+=("rollout") commands+=("label") commands+=("annotate") + commands+=("taint") commands+=("config") commands+=("cluster-info") commands+=("api-versions") diff --git a/docs/man/man1/kubectl-taint.1 b/docs/man/man1/kubectl-taint.1 new file mode 100644 index 00000000000..69342b7aa7d --- /dev/null +++ b/docs/man/man1/kubectl-taint.1 @@ -0,0 +1,203 @@ +.TH "KUBERNETES" "1" " kubernetes User Manuals" "Eric Paris" "Jan 2015" "" + + +.SH NAME +.PP +kubectl taint \- Update the taints on one or more nodes + + +.SH SYNOPSIS +.PP +\fBkubectl taint\fP [OPTIONS] + + +.SH DESCRIPTION +.PP +Update the taints on one or more nodes. + +.PP +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 253 characters. +The value must begin with a letter or number, and may contain letters, numbers, hyphens, dots, and underscores, up to 253 characters. +The effect must be NoSchedule or PreferNoSchedule. +Currently taint can only apply to node. + + +.SH OPTIONS +.PP +\fB\-\-all\fP=false + select all nodes in the cluster + +.PP +\fB\-\-include\-extended\-apis\fP=true + If true, include definitions of new APIs via calls to the API server. [default true] + +.PP +\fB\-\-no\-headers\fP=false + When using the default output, don't print headers. + +.PP +\fB\-o\fP, \fB\-\-output\fP="" + Output format. One of: json|yaml|wide|name|go\-template=...|go\-template\-file=...|jsonpath=...|jsonpath\-file=... See golang template [ +\[la]http://golang.org/pkg/text/template/#pkg-overview\[ra]] and jsonpath template [ +\[la]http://releases.k8s.io/HEAD/docs/user-guide/jsonpath.md\[ra]]. + +.PP +\fB\-\-output\-version\fP="" + Output the formatted object with the given group version (for ex: 'extensions/v1beta1'). + +.PP +\fB\-\-overwrite\fP=false + If true, allow taints to be overwritten, otherwise reject taint updates that overwrite existing taints. + +.PP +\fB\-\-schema\-cache\-dir\fP="\~/.kube/schema" + If non\-empty, load/store cached API schemas in this directory, default is '$HOME/.kube/schema' + +.PP +\fB\-l\fP, \fB\-\-selector\fP="" + Selector (label query) to filter on + +.PP +\fB\-a\fP, \fB\-\-show\-all\fP=false + When printing, show all resources (default hide terminated pods.) + +.PP +\fB\-\-show\-labels\fP=false + When printing, show all labels as the last column (default hide labels column) + +.PP +\fB\-\-sort\-by\fP="" + If non\-empty, sort list types using this field specification. The field specification is expressed as a JSONPath expression (e.g. '{.metadata.name}'). The field in the API resource specified by this JSONPath expression must be an integer or a string. + +.PP +\fB\-\-template\fP="" + Template string or path to template file to use when \-o=go\-template, \-o=go\-template\-file. The template format is golang templates [ +\[la]http://golang.org/pkg/text/template/#pkg-overview\[ra]]. + +.PP +\fB\-\-validate\fP=true + If true, use a schema to validate the input before sending it + + +.SH OPTIONS INHERITED FROM PARENT COMMANDS +.PP +\fB\-\-alsologtostderr\fP=false + log to standard error as well as files + +.PP +\fB\-\-api\-version\fP="" + DEPRECATED: The API version to use when talking to the server + +.PP +\fB\-\-as\fP="" + Username to impersonate for the operation. + +.PP +\fB\-\-certificate\-authority\fP="" + Path to a cert. file for the certificate authority. + +.PP +\fB\-\-client\-certificate\fP="" + Path to a client certificate file for TLS. + +.PP +\fB\-\-client\-key\fP="" + Path to a client key file for TLS. + +.PP +\fB\-\-cluster\fP="" + The name of the kubeconfig cluster to use + +.PP +\fB\-\-context\fP="" + The name of the kubeconfig context to use + +.PP +\fB\-\-insecure\-skip\-tls\-verify\fP=false + If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure. + +.PP +\fB\-\-kubeconfig\fP="" + Path to the kubeconfig file to use for CLI requests. + +.PP +\fB\-\-log\-backtrace\-at\fP=:0 + when logging hits line file:N, emit a stack trace + +.PP +\fB\-\-log\-dir\fP="" + If non\-empty, write log files in this directory + +.PP +\fB\-\-log\-flush\-frequency\fP=5s + Maximum number of seconds between log flushes + +.PP +\fB\-\-logtostderr\fP=true + log to standard error instead of files + +.PP +\fB\-\-match\-server\-version\fP=false + Require server version to match client version + +.PP +\fB\-\-namespace\fP="" + If present, the namespace scope for this CLI request. + +.PP +\fB\-\-password\fP="" + Password for basic authentication to the API server. + +.PP +\fB\-s\fP, \fB\-\-server\fP="" + The address and port of the Kubernetes API server + +.PP +\fB\-\-stderrthreshold\fP=2 + logs at or above this threshold go to stderr + +.PP +\fB\-\-token\fP="" + Bearer token for authentication to the API server. + +.PP +\fB\-\-user\fP="" + The name of the kubeconfig user to use + +.PP +\fB\-\-username\fP="" + Username for basic authentication to the API server. + +.PP +\fB\-\-v\fP=0 + log level for V logs + +.PP +\fB\-\-vmodule\fP= + comma\-separated list of pattern=N settings for file\-filtered logging + + +.SH EXAMPLE +.PP +.RS + +.nf +# Update node 'foo' with a taint with key 'dedicated' and value 'special\-user' and effect 'NoSchedule'. +# If a taint with that key already exists, its value and effect are replaced as specified. +kubectl taint nodes foo dedicated=special\-user:NoSchedule +# Remove from node 'foo' the taint with key 'dedicated' if one exists. +kubectl taint nodes foo dedicated\- + +.fi +.RE + + +.SH SEE ALSO +.PP +\fBkubectl(1)\fP, + + +.SH HISTORY +.PP +January 2015, Originally compiled by Eric Paris (eparis at redhat dot com) based on the kubernetes source material, but hopefully they have been automatically generated since! diff --git a/docs/man/man1/kubectl.1 b/docs/man/man1/kubectl.1 index 7a86efa6510..bcec39ce573 100644 --- a/docs/man/man1/kubectl.1 +++ b/docs/man/man1/kubectl.1 @@ -120,7 +120,7 @@ Find more information at .SH SEE ALSO .PP -\fBkubectl\-get(1)\fP, \fBkubectl\-set(1)\fP, \fBkubectl\-describe(1)\fP, \fBkubectl\-create(1)\fP, \fBkubectl\-replace(1)\fP, \fBkubectl\-patch(1)\fP, \fBkubectl\-delete(1)\fP, \fBkubectl\-edit(1)\fP, \fBkubectl\-apply(1)\fP, \fBkubectl\-namespace(1)\fP, \fBkubectl\-logs(1)\fP, \fBkubectl\-rolling\-update(1)\fP, \fBkubectl\-scale(1)\fP, \fBkubectl\-cordon(1)\fP, \fBkubectl\-drain(1)\fP, \fBkubectl\-uncordon(1)\fP, \fBkubectl\-attach(1)\fP, \fBkubectl\-exec(1)\fP, \fBkubectl\-port\-forward(1)\fP, \fBkubectl\-proxy(1)\fP, \fBkubectl\-run(1)\fP, \fBkubectl\-stop(1)\fP, \fBkubectl\-expose(1)\fP, \fBkubectl\-autoscale(1)\fP, \fBkubectl\-rollout(1)\fP, \fBkubectl\-label(1)\fP, \fBkubectl\-annotate(1)\fP, \fBkubectl\-config(1)\fP, \fBkubectl\-cluster\-info(1)\fP, \fBkubectl\-api\-versions(1)\fP, \fBkubectl\-version(1)\fP, \fBkubectl\-explain(1)\fP, \fBkubectl\-convert(1)\fP, +\fBkubectl\-get(1)\fP, \fBkubectl\-set(1)\fP, \fBkubectl\-describe(1)\fP, \fBkubectl\-create(1)\fP, \fBkubectl\-replace(1)\fP, \fBkubectl\-patch(1)\fP, \fBkubectl\-delete(1)\fP, \fBkubectl\-edit(1)\fP, \fBkubectl\-apply(1)\fP, \fBkubectl\-namespace(1)\fP, \fBkubectl\-logs(1)\fP, \fBkubectl\-rolling\-update(1)\fP, \fBkubectl\-scale(1)\fP, \fBkubectl\-cordon(1)\fP, \fBkubectl\-drain(1)\fP, \fBkubectl\-uncordon(1)\fP, \fBkubectl\-attach(1)\fP, \fBkubectl\-exec(1)\fP, \fBkubectl\-port\-forward(1)\fP, \fBkubectl\-proxy(1)\fP, \fBkubectl\-run(1)\fP, \fBkubectl\-stop(1)\fP, \fBkubectl\-expose(1)\fP, \fBkubectl\-autoscale(1)\fP, \fBkubectl\-rollout(1)\fP, \fBkubectl\-label(1)\fP, \fBkubectl\-annotate(1)\fP, \fBkubectl\-taint(1)\fP, \fBkubectl\-config(1)\fP, \fBkubectl\-cluster\-info(1)\fP, \fBkubectl\-api\-versions(1)\fP, \fBkubectl\-version(1)\fP, \fBkubectl\-explain(1)\fP, \fBkubectl\-convert(1)\fP, .SH HISTORY diff --git a/docs/user-guide/kubectl/kubectl.md b/docs/user-guide/kubectl/kubectl.md index 2298ddbbece..7b4769fb03f 100644 --- a/docs/user-guide/kubectl/kubectl.md +++ b/docs/user-guide/kubectl/kubectl.md @@ -107,10 +107,11 @@ kubectl * [kubectl run](kubectl_run.md) - Run a particular image on the cluster. * [kubectl scale](kubectl_scale.md) - Set a new size for a Deployment, ReplicaSet, Replication Controller, or Job. * [kubectl set](kubectl_set.md) - Set specific features on objects +* [kubectl taint](kubectl_taint.md) - Update the taints on one or more nodes * [kubectl uncordon](kubectl_uncordon.md) - Mark node as schedulable * [kubectl version](kubectl_version.md) - Print the client and server version information. -###### Auto generated by spf13/cobra on 10-May-2016 +###### Auto generated by spf13/cobra on 15-May-2016 [![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/docs/user-guide/kubectl/kubectl.md?pixel)]() diff --git a/docs/user-guide/kubectl/kubectl_taint.md b/docs/user-guide/kubectl/kubectl_taint.md new file mode 100644 index 00000000000..c7187d18542 --- /dev/null +++ b/docs/user-guide/kubectl/kubectl_taint.md @@ -0,0 +1,113 @@ + + + + +WARNING +WARNING +WARNING +WARNING +WARNING + +

PLEASE NOTE: This document applies to the HEAD of the source tree

+ +If you are using a released version of Kubernetes, you should +refer to the docs that go with that version. + +Documentation for other releases can be found at +[releases.k8s.io](http://releases.k8s.io). + +-- + + + + + +## kubectl taint + +Update the taints on one or more nodes + +### Synopsis + + +Update the taints on one or more nodes. + +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 253 characters. +The value must begin with a letter or number, and may contain letters, numbers, hyphens, dots, and underscores, up to 253 characters. +The effect must be NoSchedule or PreferNoSchedule. +Currently taint can only apply to node. + +``` +kubectl taint NODE NAME KEY_1=VAL_1:TAINT_EFFECT_1 ... KEY_N=VAL_N:TAINT_EFFECT_N +``` + +### Examples + +``` +# Update node 'foo' with a taint with key 'dedicated' and value 'special-user' and effect 'NoSchedule'. +# If a taint with that key already exists, its value and effect are replaced as specified. +kubectl taint nodes foo dedicated=special-user:NoSchedule +# Remove from node 'foo' the taint with key 'dedicated' if one exists. +kubectl taint nodes foo dedicated- +``` + +### Options + +``` + --all[=false]: select all nodes in the cluster + --include-extended-apis[=true]: If true, include definitions of new APIs via calls to the API server. [default true] + --no-headers[=false]: When using the default output, don't print headers. + -o, --output="": Output format. One of: json|yaml|wide|name|go-template=...|go-template-file=...|jsonpath=...|jsonpath-file=... See golang template [http://golang.org/pkg/text/template/#pkg-overview] and jsonpath template [http://releases.k8s.io/HEAD/docs/user-guide/jsonpath.md]. + --output-version="": Output the formatted object with the given group version (for ex: 'extensions/v1beta1'). + --overwrite[=false]: If true, allow taints to be overwritten, otherwise reject taint updates that overwrite existing taints. + --schema-cache-dir="~/.kube/schema": If non-empty, load/store cached API schemas in this directory, default is '$HOME/.kube/schema' + -l, --selector="": Selector (label query) to filter on + -a, --show-all[=false]: When printing, show all resources (default hide terminated pods.) + --show-labels[=false]: When printing, show all labels as the last column (default hide labels column) + --sort-by="": If non-empty, sort list types using this field specification. The field specification is expressed as a JSONPath expression (e.g. '{.metadata.name}'). The field in the API resource specified by this JSONPath expression must be an integer or a string. + --template="": Template string or path to template file to use when -o=go-template, -o=go-template-file. The template format is golang templates [http://golang.org/pkg/text/template/#pkg-overview]. + --validate[=true]: If true, use a schema to validate the input before sending it +``` + +### Options inherited from parent commands + +``` + --alsologtostderr[=false]: log to standard error as well as files + --as="": Username to impersonate for the operation. + --certificate-authority="": Path to a cert. file for the certificate authority. + --client-certificate="": Path to a client certificate file for TLS. + --client-key="": Path to a client key file for TLS. + --cluster="": The name of the kubeconfig cluster to use + --context="": The name of the kubeconfig context to use + --insecure-skip-tls-verify[=false]: If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure. + --kubeconfig="": Path to the kubeconfig file to use for CLI requests. + --log-backtrace-at=:0: when logging hits line file:N, emit a stack trace + --log-dir="": If non-empty, write log files in this directory + --log-flush-frequency=5s: Maximum number of seconds between log flushes + --logtostderr[=true]: log to standard error instead of files + --match-server-version[=false]: Require server version to match client version + --namespace="": If present, the namespace scope for this CLI request. + --password="": Password for basic authentication to the API server. + -s, --server="": The address and port of the Kubernetes API server + --stderrthreshold=2: logs at or above this threshold go to stderr + --token="": Bearer token for authentication to the API server. + --user="": The name of the kubeconfig user to use + --username="": Username for basic authentication to the API server. + --v=0: log level for V logs + --vmodule=: comma-separated list of pattern=N settings for file-filtered logging +``` + +### SEE ALSO + +* [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager + +###### Auto generated by spf13/cobra on 17-May-2016 + + +[![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/docs/user-guide/kubectl/kubectl_taint.md?pixel)]() + diff --git a/docs/yaml/kubectl/kubectl.yaml b/docs/yaml/kubectl/kubectl.yaml index 04fa1ce7e75..fed3700fee2 100644 --- a/docs/yaml/kubectl/kubectl.yaml +++ b/docs/yaml/kubectl/kubectl.yaml @@ -93,6 +93,7 @@ see_also: - rollout - label - annotate +- taint - config - cluster-info - api-versions diff --git a/docs/yaml/kubectl/kubectl_taint.yaml b/docs/yaml/kubectl/kubectl_taint.yaml new file mode 100644 index 00000000000..87f0aeaae96 --- /dev/null +++ b/docs/yaml/kubectl/kubectl_taint.yaml @@ -0,0 +1,127 @@ +name: taint +synopsis: Update the taints on one or more nodes +description: |- + Update the taints on one or more nodes. + + 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 253 characters. + The value must begin with a letter or number, and may contain letters, numbers, hyphens, dots, and underscores, up to 253 characters. + The effect must be NoSchedule or PreferNoSchedule. + Currently taint can only apply to node. +options: +- name: all + default_value: "false" + usage: select all nodes in the cluster +- name: include-extended-apis + default_value: "true" + usage: | + If true, include definitions of new APIs via calls to the API server. [default true] +- name: no-headers + default_value: "false" + usage: When using the default output, don't print headers. +- name: output + shorthand: o + usage: | + Output format. One of: json|yaml|wide|name|go-template=...|go-template-file=...|jsonpath=...|jsonpath-file=... See golang template [http://golang.org/pkg/text/template/#pkg-overview] and jsonpath template [http://releases.k8s.io/HEAD/docs/user-guide/jsonpath.md]. +- name: output-version + usage: | + Output the formatted object with the given group version (for ex: 'extensions/v1beta1'). +- name: overwrite + default_value: "false" + usage: | + If true, allow taints to be overwritten, otherwise reject taint updates that overwrite existing taints. +- name: schema-cache-dir + default_value: ~/.kube/schema + usage: | + If non-empty, load/store cached API schemas in this directory, default is '$HOME/.kube/schema' +- name: selector + shorthand: l + usage: Selector (label query) to filter on +- name: show-all + shorthand: a + default_value: "false" + usage: | + When printing, show all resources (default hide terminated pods.) +- name: show-labels + default_value: "false" + usage: | + When printing, show all labels as the last column (default hide labels column) +- name: sort-by + usage: | + If non-empty, sort list types using this field specification. The field specification is expressed as a JSONPath expression (e.g. '{.metadata.name}'). The field in the API resource specified by this JSONPath expression must be an integer or a string. +- name: template + usage: | + Template string or path to template file to use when -o=go-template, -o=go-template-file. The template format is golang templates [http://golang.org/pkg/text/template/#pkg-overview]. +- name: validate + default_value: "true" + usage: | + If true, use a schema to validate the input before sending it +inherited_options: +- name: alsologtostderr + default_value: "false" + usage: log to standard error as well as files +- name: api-version + usage: | + DEPRECATED: The API version to use when talking to the server +- name: as + usage: Username to impersonate for the operation. +- name: certificate-authority + usage: Path to a cert. file for the certificate authority. +- name: client-certificate + usage: Path to a client certificate file for TLS. +- name: client-key + usage: Path to a client key file for TLS. +- name: cluster + usage: The name of the kubeconfig cluster to use +- name: context + usage: The name of the kubeconfig context to use +- name: insecure-skip-tls-verify + default_value: "false" + usage: | + If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure. +- name: kubeconfig + usage: Path to the kubeconfig file to use for CLI requests. +- name: log-backtrace-at + default_value: :0 + usage: when logging hits line file:N, emit a stack trace +- name: log-dir + usage: If non-empty, write log files in this directory +- name: log-flush-frequency + default_value: 5s + usage: Maximum number of seconds between log flushes +- name: logtostderr + default_value: "true" + usage: log to standard error instead of files +- name: match-server-version + default_value: "false" + usage: Require server version to match client version +- name: namespace + usage: If present, the namespace scope for this CLI request. +- name: password + usage: Password for basic authentication to the API server. +- name: server + shorthand: s + usage: The address and port of the Kubernetes API server +- name: stderrthreshold + default_value: "2" + usage: logs at or above this threshold go to stderr +- name: token + usage: Bearer token for authentication to the API server. +- name: user + usage: The name of the kubeconfig user to use +- name: username + usage: Username for basic authentication to the API server. +- name: v + default_value: "0" + usage: log level for V logs +- name: vmodule + usage: | + comma-separated list of pattern=N settings for file-filtered logging +example: |- + # Update node 'foo' with a taint with key 'dedicated' and value 'special-user' and effect 'NoSchedule'. + # If a taint with that key already exists, its value and effect are replaced as specified. + kubectl taint nodes foo dedicated=special-user:NoSchedule + # Remove from node 'foo' the taint with key 'dedicated' if one exists. + kubectl taint nodes foo dedicated- +see_also: +- kubectl diff --git a/pkg/api/deep_copy_generated.go b/pkg/api/deep_copy_generated.go index e7bceba9013..152c26808fb 100644 --- a/pkg/api/deep_copy_generated.go +++ b/pkg/api/deep_copy_generated.go @@ -175,6 +175,8 @@ func init() { DeepCopy_api_ServiceSpec, DeepCopy_api_ServiceStatus, DeepCopy_api_TCPSocketAction, + DeepCopy_api_Taint, + DeepCopy_api_Toleration, DeepCopy_api_Volume, DeepCopy_api_VolumeMount, DeepCopy_api_VolumeSource, @@ -2967,6 +2969,21 @@ func DeepCopy_api_TCPSocketAction(in TCPSocketAction, out *TCPSocketAction, c *c return nil } +func DeepCopy_api_Taint(in Taint, out *Taint, c *conversion.Cloner) error { + out.Key = in.Key + out.Value = in.Value + out.Effect = in.Effect + return nil +} + +func DeepCopy_api_Toleration(in Toleration, out *Toleration, c *conversion.Cloner) error { + out.Key = in.Key + out.Operator = in.Operator + out.Value = in.Value + out.Effect = in.Effect + return nil +} + func DeepCopy_api_Volume(in Volume, out *Volume, c *conversion.Cloner) error { out.Name = in.Name if err := DeepCopy_api_VolumeSource(in.VolumeSource, &out.VolumeSource, c); err != nil { diff --git a/pkg/api/helpers.go b/pkg/api/helpers.go index 96b7db58a88..070e39630de 100644 --- a/pkg/api/helpers.go +++ b/pkg/api/helpers.go @@ -411,9 +411,19 @@ func NodeSelectorRequirementsAsSelector(nsm []NodeSelectorRequirement) (labels.S return selector, nil } -// AffinityAnnotationKey represents the key of affinity data (json serialized) -// in the Annotations of a Pod. -const AffinityAnnotationKey string = "scheduler.alpha.kubernetes.io/affinity" +const ( + // AffinityAnnotationKey represents the key of affinity data (json serialized) + // in the Annotations of a Pod. + AffinityAnnotationKey string = "scheduler.alpha.kubernetes.io/affinity" + + // TolerationsAnnotationKey represents the key of tolerations data (json serialized) + // in the Annotations of a Pod. + TolerationsAnnotationKey string = "scheduler.alpha.kubernetes.io/tolerations" + + // TaintsAnnotationKey represents the key of taints data (json serialized) + // in the Annotations of a Node. + TaintsAnnotationKey string = "scheduler.alpha.kubernetes.io/taints" +) // GetAffinityFromPod gets the json serialized affinity data from Pod.Annotations // and converts it to the Affinity type in api. @@ -427,3 +437,61 @@ func GetAffinityFromPodAnnotations(annotations map[string]string) (Affinity, err } return affinity, 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) ([]Toleration, error) { + var tolerations []Toleration + if len(annotations) > 0 && annotations[TolerationsAnnotationKey] != "" { + err := json.Unmarshal([]byte(annotations[TolerationsAnnotationKey]), &tolerations) + if err != nil { + return tolerations, err + } + } + return tolerations, nil +} + +// 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) ([]Taint, error) { + var taints []Taint + if len(annotations) > 0 && annotations[TaintsAnnotationKey] != "" { + err := json.Unmarshal([]byte(annotations[TaintsAnnotationKey]), &taints) + if err != nil { + return []Taint{}, err + } + } + return taints, nil +} + +// TolerationToleratesTaint checks if the toleration tolerates the taint. +func TolerationToleratesTaint(toleration Toleration, taint 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 == TolerationOpEqual) && toleration.Value == taint.Value { + return true + } + if toleration.Operator == TolerationOpExists { + return true + } + return false + +} + +// TaintToleratedByTolerations checks if taint is tolerated by any of the tolerations. +func TaintToleratedByTolerations(taint Taint, tolerations []Toleration) bool { + tolerated := false + for _, toleration := range tolerations { + if TolerationToleratesTaint(toleration, taint) { + tolerated = true + break + } + } + return tolerated +} diff --git a/pkg/api/types.generated.go b/pkg/api/types.generated.go index 3f7b9e55173..5632f87f097 100644 --- a/pkg/api/types.generated.go +++ b/pkg/api/types.generated.go @@ -24521,6 +24521,592 @@ func (x *PreferredSchedulingTerm) codecDecodeSelfFromArray(l int, d *codec1978.D z.DecSendContainerState(codecSelfer_containerArrayEnd1234) } +func (x *Taint) CodecEncodeSelf(e *codec1978.Encoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperEncoder(e) + _, _, _ = h, z, r + if x == nil { + r.EncodeNil() + } else { + yym1 := z.EncBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.EncExt(x) { + } else { + yysep2 := !z.EncBinary() + yy2arr2 := z.EncBasicHandle().StructToArray + var yyq2 [3]bool + _, _, _ = yysep2, yyq2, yy2arr2 + const yyr2 bool = false + yyq2[1] = x.Value != "" + var yynn2 int + if yyr2 || yy2arr2 { + r.EncodeArrayStart(3) + } else { + yynn2 = 2 + for _, b := range yyq2 { + if b { + yynn2++ + } + } + r.EncodeMapStart(yynn2) + yynn2 = 0 + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + yym4 := z.EncBinary() + _ = yym4 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.Key)) + } + } else { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("key")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yym5 := z.EncBinary() + _ = yym5 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.Key)) + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[1] { + yym7 := z.EncBinary() + _ = yym7 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.Value)) + } + } else { + r.EncodeString(codecSelferC_UTF81234, "") + } + } else { + if yyq2[1] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("value")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yym8 := z.EncBinary() + _ = yym8 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.Value)) + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + x.Effect.CodecEncodeSelf(e) + } else { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("effect")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + x.Effect.CodecEncodeSelf(e) + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayEnd1234) + } else { + z.EncSendContainerState(codecSelfer_containerMapEnd1234) + } + } + } +} + +func (x *Taint) CodecDecodeSelf(d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + yym1 := z.DecBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.DecExt(x) { + } else { + yyct2 := r.ContainerType() + if yyct2 == codecSelferValueTypeMap1234 { + yyl2 := r.ReadMapStart() + if yyl2 == 0 { + z.DecSendContainerState(codecSelfer_containerMapEnd1234) + } else { + x.codecDecodeSelfFromMap(yyl2, d) + } + } else if yyct2 == codecSelferValueTypeArray1234 { + yyl2 := r.ReadArrayStart() + if yyl2 == 0 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + } else { + x.codecDecodeSelfFromArray(yyl2, d) + } + } else { + panic(codecSelferOnlyMapOrArrayEncodeToStructErr1234) + } + } +} + +func (x *Taint) codecDecodeSelfFromMap(l int, d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + var yys3Slc = z.DecScratchBuffer() // default slice to decode into + _ = yys3Slc + var yyhl3 bool = l >= 0 + for yyj3 := 0; ; yyj3++ { + if yyhl3 { + if yyj3 >= l { + break + } + } else { + if r.CheckBreak() { + break + } + } + z.DecSendContainerState(codecSelfer_containerMapKey1234) + yys3Slc = r.DecodeBytes(yys3Slc, true, true) + yys3 := string(yys3Slc) + z.DecSendContainerState(codecSelfer_containerMapValue1234) + switch yys3 { + case "key": + if r.TryDecodeAsNil() { + x.Key = "" + } else { + x.Key = string(r.DecodeString()) + } + case "value": + if r.TryDecodeAsNil() { + x.Value = "" + } else { + x.Value = string(r.DecodeString()) + } + case "effect": + if r.TryDecodeAsNil() { + x.Effect = "" + } else { + x.Effect = TaintEffect(r.DecodeString()) + } + default: + z.DecStructFieldNotFound(-1, yys3) + } // end switch yys3 + } // end for yyj3 + z.DecSendContainerState(codecSelfer_containerMapEnd1234) +} + +func (x *Taint) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + var yyj7 int + var yyb7 bool + var yyhl7 bool = l >= 0 + yyj7++ + if yyhl7 { + yyb7 = yyj7 > l + } else { + yyb7 = r.CheckBreak() + } + if yyb7 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.Key = "" + } else { + x.Key = string(r.DecodeString()) + } + yyj7++ + if yyhl7 { + yyb7 = yyj7 > l + } else { + yyb7 = r.CheckBreak() + } + if yyb7 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.Value = "" + } else { + x.Value = string(r.DecodeString()) + } + yyj7++ + if yyhl7 { + yyb7 = yyj7 > l + } else { + yyb7 = r.CheckBreak() + } + if yyb7 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.Effect = "" + } else { + x.Effect = TaintEffect(r.DecodeString()) + } + for { + yyj7++ + if yyhl7 { + yyb7 = yyj7 > l + } else { + yyb7 = r.CheckBreak() + } + if yyb7 { + break + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + z.DecStructFieldNotFound(yyj7-1, "") + } + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) +} + +func (x TaintEffect) CodecEncodeSelf(e *codec1978.Encoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperEncoder(e) + _, _, _ = h, z, r + yym1 := z.EncBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.EncExt(x) { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x)) + } +} + +func (x *TaintEffect) CodecDecodeSelf(d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + yym1 := z.DecBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.DecExt(x) { + } else { + *((*string)(x)) = r.DecodeString() + } +} + +func (x *Toleration) CodecEncodeSelf(e *codec1978.Encoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperEncoder(e) + _, _, _ = h, z, r + if x == nil { + r.EncodeNil() + } else { + yym1 := z.EncBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.EncExt(x) { + } else { + yysep2 := !z.EncBinary() + yy2arr2 := z.EncBasicHandle().StructToArray + var yyq2 [4]bool + _, _, _ = yysep2, yyq2, yy2arr2 + const yyr2 bool = false + yyq2[0] = x.Key != "" + yyq2[1] = x.Operator != "" + yyq2[2] = x.Value != "" + yyq2[3] = x.Effect != "" + var yynn2 int + if yyr2 || yy2arr2 { + r.EncodeArrayStart(4) + } else { + yynn2 = 0 + for _, b := range yyq2 { + if b { + yynn2++ + } + } + r.EncodeMapStart(yynn2) + yynn2 = 0 + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[0] { + yym4 := z.EncBinary() + _ = yym4 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.Key)) + } + } else { + r.EncodeString(codecSelferC_UTF81234, "") + } + } else { + if yyq2[0] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("key")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yym5 := z.EncBinary() + _ = yym5 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.Key)) + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[1] { + x.Operator.CodecEncodeSelf(e) + } else { + r.EncodeString(codecSelferC_UTF81234, "") + } + } else { + if yyq2[1] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("operator")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + x.Operator.CodecEncodeSelf(e) + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[2] { + yym10 := z.EncBinary() + _ = yym10 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.Value)) + } + } else { + r.EncodeString(codecSelferC_UTF81234, "") + } + } else { + if yyq2[2] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("value")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yym11 := z.EncBinary() + _ = yym11 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.Value)) + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[3] { + x.Effect.CodecEncodeSelf(e) + } else { + r.EncodeString(codecSelferC_UTF81234, "") + } + } else { + if yyq2[3] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("effect")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + x.Effect.CodecEncodeSelf(e) + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayEnd1234) + } else { + z.EncSendContainerState(codecSelfer_containerMapEnd1234) + } + } + } +} + +func (x *Toleration) CodecDecodeSelf(d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + yym1 := z.DecBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.DecExt(x) { + } else { + yyct2 := r.ContainerType() + if yyct2 == codecSelferValueTypeMap1234 { + yyl2 := r.ReadMapStart() + if yyl2 == 0 { + z.DecSendContainerState(codecSelfer_containerMapEnd1234) + } else { + x.codecDecodeSelfFromMap(yyl2, d) + } + } else if yyct2 == codecSelferValueTypeArray1234 { + yyl2 := r.ReadArrayStart() + if yyl2 == 0 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + } else { + x.codecDecodeSelfFromArray(yyl2, d) + } + } else { + panic(codecSelferOnlyMapOrArrayEncodeToStructErr1234) + } + } +} + +func (x *Toleration) codecDecodeSelfFromMap(l int, d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + var yys3Slc = z.DecScratchBuffer() // default slice to decode into + _ = yys3Slc + var yyhl3 bool = l >= 0 + for yyj3 := 0; ; yyj3++ { + if yyhl3 { + if yyj3 >= l { + break + } + } else { + if r.CheckBreak() { + break + } + } + z.DecSendContainerState(codecSelfer_containerMapKey1234) + yys3Slc = r.DecodeBytes(yys3Slc, true, true) + yys3 := string(yys3Slc) + z.DecSendContainerState(codecSelfer_containerMapValue1234) + switch yys3 { + case "key": + if r.TryDecodeAsNil() { + x.Key = "" + } else { + x.Key = string(r.DecodeString()) + } + case "operator": + if r.TryDecodeAsNil() { + x.Operator = "" + } else { + x.Operator = TolerationOperator(r.DecodeString()) + } + case "value": + if r.TryDecodeAsNil() { + x.Value = "" + } else { + x.Value = string(r.DecodeString()) + } + case "effect": + if r.TryDecodeAsNil() { + x.Effect = "" + } else { + x.Effect = TaintEffect(r.DecodeString()) + } + default: + z.DecStructFieldNotFound(-1, yys3) + } // end switch yys3 + } // end for yyj3 + z.DecSendContainerState(codecSelfer_containerMapEnd1234) +} + +func (x *Toleration) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + var yyj8 int + var yyb8 bool + var yyhl8 bool = l >= 0 + yyj8++ + if yyhl8 { + yyb8 = yyj8 > l + } else { + yyb8 = r.CheckBreak() + } + if yyb8 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.Key = "" + } else { + x.Key = string(r.DecodeString()) + } + yyj8++ + if yyhl8 { + yyb8 = yyj8 > l + } else { + yyb8 = r.CheckBreak() + } + if yyb8 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.Operator = "" + } else { + x.Operator = TolerationOperator(r.DecodeString()) + } + yyj8++ + if yyhl8 { + yyb8 = yyj8 > l + } else { + yyb8 = r.CheckBreak() + } + if yyb8 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.Value = "" + } else { + x.Value = string(r.DecodeString()) + } + yyj8++ + if yyhl8 { + yyb8 = yyj8 > l + } else { + yyb8 = r.CheckBreak() + } + if yyb8 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.Effect = "" + } else { + x.Effect = TaintEffect(r.DecodeString()) + } + for { + yyj8++ + if yyhl8 { + yyb8 = yyj8 > l + } else { + yyb8 = r.CheckBreak() + } + if yyb8 { + break + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + z.DecStructFieldNotFound(yyj8-1, "") + } + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) +} + +func (x TolerationOperator) CodecEncodeSelf(e *codec1978.Encoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperEncoder(e) + _, _, _ = h, z, r + yym1 := z.EncBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.EncExt(x) { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x)) + } +} + +func (x *TolerationOperator) CodecDecodeSelf(d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + yym1 := z.DecBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.DecExt(x) { + } else { + *((*string)(x)) = r.DecodeString() + } +} + func (x *PodSpec) CodecEncodeSelf(e *codec1978.Encoder) { var h codecSelfer1234 z, r := codec1978.GenHelperEncoder(e) diff --git a/pkg/api/types.go b/pkg/api/types.go index 3b3435b49a0..05893575269 100644 --- a/pkg/api/types.go +++ b/pkg/api/types.go @@ -1308,6 +1308,73 @@ type PreferredSchedulingTerm struct { Preference NodeSelectorTerm `json:"preference"` } +// The node this Taint is attached to has the effect "effect" on +// any pod that that does not tolerate the Taint. +type Taint struct { + // Required. The taint key to be applied to a node. + Key string `json:"key" patchStrategy:"merge" patchMergeKey:"key"` + // Required. The taint value corresponding to the taint key. + Value string `json:"value,omitempty"` + // Required. The effect of the taint on pods + // that do not tolerate the taint. + // Valid effects are NoSchedule and PreferNoSchedule. + Effect TaintEffect `json:"effect"` +} + +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. + // Do not allow new pods to schedule onto the node unless they tolerate the taint, + // do not allow pods to start on Kubelet unless they tolerate the taint, + // but allow all already-running pods to continue running. + // Enforced by the scheduler and Kubelet. + // TaintEffectNoScheduleNoAdmit TaintEffect = "NoScheduleNoAdmit" + // NOT YET IMPLEMENTED. TODO: Uncomment field once it is implemented. + // Do not allow new pods to schedule onto the node unless they tolerate the taint, + // do not allow pods to start on Kubelet unless they tolerate the taint, + // and evict any already-running pods that do not tolerate the taint. + // Enforced by the scheduler and Kubelet. + // TaintEffectNoScheduleNoAdmitNoExecute = "NoScheduleNoAdmitNoExecute" +) + +// The pod this Toleration is attached to tolerates any taint that matches +// the triple using the matching operator . +type Toleration struct { + // Required. Key is the taint key that the toleration applies to. + Key string `json:"key,omitempty" patchStrategy:"merge" patchMergeKey:"key"` + // 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. + Operator TolerationOperator `json:"operator,omitempty"` + // Value is the taint value the toleration matches to. + // If the operator is Exists, the value should be empty, otherwise just a regular string. + Value string `json:"value,omitempty"` + // Effect indicates the taint effect to match. Empty means match all taint effects. + // When specified, allowed values are NoSchedule and PreferNoSchedule. + Effect TaintEffect `json:"effect,omitempty"` + // TODO: For forgiveness (#1574), we'd eventually add at least a grace period + // here, and possibly an occurrence threshold and period. +} + +// 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 `json:"volumes"` diff --git a/pkg/api/v1/conversion_generated.go b/pkg/api/v1/conversion_generated.go index 6f7c708d35e..bdd4bafab8d 100644 --- a/pkg/api/v1/conversion_generated.go +++ b/pkg/api/v1/conversion_generated.go @@ -311,6 +311,10 @@ func init() { Convert_api_ServiceStatus_To_v1_ServiceStatus, 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, @@ -6548,6 +6552,52 @@ func Convert_api_TCPSocketAction_To_v1_TCPSocketAction(in *api.TCPSocketAction, return autoConvert_api_TCPSocketAction_To_v1_TCPSocketAction(in, out, s) } +func autoConvert_v1_Taint_To_api_Taint(in *Taint, out *api.Taint, s conversion.Scope) error { + out.Key = in.Key + out.Value = in.Value + out.Effect = api.TaintEffect(in.Effect) + return nil +} + +func Convert_v1_Taint_To_api_Taint(in *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 *Taint, s conversion.Scope) error { + out.Key = in.Key + out.Value = in.Value + out.Effect = TaintEffect(in.Effect) + return nil +} + +func Convert_api_Taint_To_v1_Taint(in *api.Taint, out *Taint, s conversion.Scope) error { + return autoConvert_api_Taint_To_v1_Taint(in, out, s) +} + +func autoConvert_v1_Toleration_To_api_Toleration(in *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) + return nil +} + +func Convert_v1_Toleration_To_api_Toleration(in *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 *Toleration, s conversion.Scope) error { + out.Key = in.Key + out.Operator = TolerationOperator(in.Operator) + out.Value = in.Value + out.Effect = TaintEffect(in.Effect) + return nil +} + +func Convert_api_Toleration_To_v1_Toleration(in *api.Toleration, out *Toleration, s conversion.Scope) error { + return autoConvert_api_Toleration_To_v1_Toleration(in, out, s) +} + func autoConvert_v1_Volume_To_api_Volume(in *Volume, out *api.Volume, s conversion.Scope) error { SetDefaults_Volume(in) out.Name = in.Name diff --git a/pkg/api/v1/deep_copy_generated.go b/pkg/api/v1/deep_copy_generated.go index 4fef3f5f23d..15939d98ce5 100644 --- a/pkg/api/v1/deep_copy_generated.go +++ b/pkg/api/v1/deep_copy_generated.go @@ -172,6 +172,8 @@ func init() { DeepCopy_v1_ServiceSpec, DeepCopy_v1_ServiceStatus, DeepCopy_v1_TCPSocketAction, + DeepCopy_v1_Taint, + DeepCopy_v1_Toleration, DeepCopy_v1_Volume, DeepCopy_v1_VolumeMount, DeepCopy_v1_VolumeSource, @@ -2928,6 +2930,21 @@ func DeepCopy_v1_TCPSocketAction(in TCPSocketAction, out *TCPSocketAction, c *co return nil } +func DeepCopy_v1_Taint(in Taint, out *Taint, c *conversion.Cloner) error { + out.Key = in.Key + out.Value = in.Value + out.Effect = in.Effect + return nil +} + +func DeepCopy_v1_Toleration(in Toleration, out *Toleration, c *conversion.Cloner) error { + out.Key = in.Key + out.Operator = in.Operator + out.Value = in.Value + out.Effect = in.Effect + return nil +} + func DeepCopy_v1_Volume(in Volume, out *Volume, c *conversion.Cloner) error { out.Name = in.Name if err := DeepCopy_v1_VolumeSource(in.VolumeSource, &out.VolumeSource, c); err != nil { diff --git a/pkg/api/v1/generated.pb.go b/pkg/api/v1/generated.pb.go index aa69945f828..01c2113ab12 100644 --- a/pkg/api/v1/generated.pb.go +++ b/pkg/api/v1/generated.pb.go @@ -165,6 +165,8 @@ limitations under the License. ServiceSpec ServiceStatus TCPSocketAction + Taint + Toleration Volume VolumeMount VolumeSource @@ -749,6 +751,14 @@ func (m *TCPSocketAction) Reset() { *m = TCPSocketAction{} } func (m *TCPSocketAction) String() string { return proto.CompactTextString(m) } func (*TCPSocketAction) ProtoMessage() {} +func (m *Taint) Reset() { *m = Taint{} } +func (m *Taint) String() string { return proto.CompactTextString(m) } +func (*Taint) ProtoMessage() {} + +func (m *Toleration) Reset() { *m = Toleration{} } +func (m *Toleration) String() string { return proto.CompactTextString(m) } +func (*Toleration) ProtoMessage() {} + func (m *Volume) Reset() { *m = Volume{} } func (m *Volume) String() string { return proto.CompactTextString(m) } func (*Volume) ProtoMessage() {} @@ -906,6 +916,8 @@ func init() { proto.RegisterType((*ServiceSpec)(nil), "k8s.io.kubernetes.pkg.api.v1.ServiceSpec") proto.RegisterType((*ServiceStatus)(nil), "k8s.io.kubernetes.pkg.api.v1.ServiceStatus") proto.RegisterType((*TCPSocketAction)(nil), "k8s.io.kubernetes.pkg.api.v1.TCPSocketAction") + proto.RegisterType((*Taint)(nil), "k8s.io.kubernetes.pkg.api.v1.Taint") + proto.RegisterType((*Toleration)(nil), "k8s.io.kubernetes.pkg.api.v1.Toleration") proto.RegisterType((*Volume)(nil), "k8s.io.kubernetes.pkg.api.v1.Volume") proto.RegisterType((*VolumeMount)(nil), "k8s.io.kubernetes.pkg.api.v1.VolumeMount") proto.RegisterType((*VolumeSource)(nil), "k8s.io.kubernetes.pkg.api.v1.VolumeSource") @@ -7111,6 +7123,70 @@ func (m *TCPSocketAction) MarshalTo(data []byte) (int, error) { return i, nil } +func (m *Taint) 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 *Taint) 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.Value))) + i += copy(data[i:], m.Value) + data[i] = 0x1a + i++ + i = encodeVarintGenerated(data, i, uint64(len(m.Effect))) + i += copy(data[i:], m.Effect) + return i, nil +} + +func (m *Toleration) 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 *Toleration) 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) + data[i] = 0x1a + i++ + i = encodeVarintGenerated(data, i, uint64(len(m.Value))) + i += copy(data[i:], m.Value) + data[i] = 0x22 + i++ + i = encodeVarintGenerated(data, i, uint64(len(m.Effect))) + i += copy(data[i:], m.Effect) + return i, nil +} + func (m *Volume) Marshal() (data []byte, err error) { size := m.Size() data = make([]byte, size) @@ -9724,6 +9800,32 @@ func (m *TCPSocketAction) Size() (n int) { return n } +func (m *Taint) Size() (n int) { + var l int + _ = l + l = len(m.Key) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.Value) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.Effect) + n += 1 + l + sovGenerated(uint64(l)) + return n +} + +func (m *Toleration) 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)) + l = len(m.Value) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.Effect) + n += 1 + l + sovGenerated(uint64(l)) + return n +} + func (m *Volume) Size() (n int) { var l int _ = l @@ -32142,6 +32244,309 @@ func (m *TCPSocketAction) Unmarshal(data []byte) error { } return nil } +func (m *Taint) 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: Taint: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Taint: 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 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 + } + m.Value = string(data[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Effect", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Effect = TaintEffect(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 *Toleration) 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: Toleration: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Toleration: 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 = TolerationOperator(data[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + 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 + } + m.Value = string(data[iNdEx:postIndex]) + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Effect", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Effect = TaintEffect(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 *Volume) Unmarshal(data []byte) error { l := len(data) iNdEx := 0 diff --git a/pkg/api/v1/generated.proto b/pkg/api/v1/generated.proto index 875bea9af33..a821388b6bc 100644 --- a/pkg/api/v1/generated.proto +++ b/pkg/api/v1/generated.proto @@ -2686,6 +2686,42 @@ message TCPSocketAction { optional k8s.io.kubernetes.pkg.util.intstr.IntOrString port = 1; } +// The node this Taint is attached to has the effect "effect" on +// any pod that that does not tolerate the Taint. +message Taint { + // Required. The taint key to be applied to a node. + optional string key = 1; + + // Required. The taint value corresponding to the taint key. + optional string value = 2; + + // Required. The effect of the taint on pods + // that do not tolerate the taint. + // Valid effects are NoSchedule and PreferNoSchedule. + optional string effect = 3; +} + +// The pod this Toleration is attached to tolerates any taint that matches +// the triple using the matching operator . +message Toleration { + // Required. Key is the taint key that the toleration applies to. + optional string key = 1; + + // 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 string operator = 2; + + // 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 string value = 3; + + // Effect indicates the taint effect to match. Empty means match all taint effects. + // When specified, allowed values are NoSchedule and PreferNoSchedule. + optional string effect = 4; +} + // Volume represents a named volume in a pod that may be accessed by any container in the pod. message Volume { // Volume's name. diff --git a/pkg/api/v1/types.generated.go b/pkg/api/v1/types.generated.go index bdc5f75238c..f5e7f1531dc 100644 --- a/pkg/api/v1/types.generated.go +++ b/pkg/api/v1/types.generated.go @@ -23801,6 +23801,592 @@ func (x *PreferredSchedulingTerm) codecDecodeSelfFromArray(l int, d *codec1978.D z.DecSendContainerState(codecSelfer_containerArrayEnd1234) } +func (x *Taint) CodecEncodeSelf(e *codec1978.Encoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperEncoder(e) + _, _, _ = h, z, r + if x == nil { + r.EncodeNil() + } else { + yym1 := z.EncBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.EncExt(x) { + } else { + yysep2 := !z.EncBinary() + yy2arr2 := z.EncBasicHandle().StructToArray + var yyq2 [3]bool + _, _, _ = yysep2, yyq2, yy2arr2 + const yyr2 bool = false + yyq2[1] = x.Value != "" + var yynn2 int + if yyr2 || yy2arr2 { + r.EncodeArrayStart(3) + } else { + yynn2 = 2 + for _, b := range yyq2 { + if b { + yynn2++ + } + } + r.EncodeMapStart(yynn2) + yynn2 = 0 + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + yym4 := z.EncBinary() + _ = yym4 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.Key)) + } + } else { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("key")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yym5 := z.EncBinary() + _ = yym5 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.Key)) + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[1] { + yym7 := z.EncBinary() + _ = yym7 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.Value)) + } + } else { + r.EncodeString(codecSelferC_UTF81234, "") + } + } else { + if yyq2[1] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("value")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yym8 := z.EncBinary() + _ = yym8 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.Value)) + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + x.Effect.CodecEncodeSelf(e) + } else { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("effect")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + x.Effect.CodecEncodeSelf(e) + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayEnd1234) + } else { + z.EncSendContainerState(codecSelfer_containerMapEnd1234) + } + } + } +} + +func (x *Taint) CodecDecodeSelf(d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + yym1 := z.DecBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.DecExt(x) { + } else { + yyct2 := r.ContainerType() + if yyct2 == codecSelferValueTypeMap1234 { + yyl2 := r.ReadMapStart() + if yyl2 == 0 { + z.DecSendContainerState(codecSelfer_containerMapEnd1234) + } else { + x.codecDecodeSelfFromMap(yyl2, d) + } + } else if yyct2 == codecSelferValueTypeArray1234 { + yyl2 := r.ReadArrayStart() + if yyl2 == 0 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + } else { + x.codecDecodeSelfFromArray(yyl2, d) + } + } else { + panic(codecSelferOnlyMapOrArrayEncodeToStructErr1234) + } + } +} + +func (x *Taint) codecDecodeSelfFromMap(l int, d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + var yys3Slc = z.DecScratchBuffer() // default slice to decode into + _ = yys3Slc + var yyhl3 bool = l >= 0 + for yyj3 := 0; ; yyj3++ { + if yyhl3 { + if yyj3 >= l { + break + } + } else { + if r.CheckBreak() { + break + } + } + z.DecSendContainerState(codecSelfer_containerMapKey1234) + yys3Slc = r.DecodeBytes(yys3Slc, true, true) + yys3 := string(yys3Slc) + z.DecSendContainerState(codecSelfer_containerMapValue1234) + switch yys3 { + case "key": + if r.TryDecodeAsNil() { + x.Key = "" + } else { + x.Key = string(r.DecodeString()) + } + case "value": + if r.TryDecodeAsNil() { + x.Value = "" + } else { + x.Value = string(r.DecodeString()) + } + case "effect": + if r.TryDecodeAsNil() { + x.Effect = "" + } else { + x.Effect = TaintEffect(r.DecodeString()) + } + default: + z.DecStructFieldNotFound(-1, yys3) + } // end switch yys3 + } // end for yyj3 + z.DecSendContainerState(codecSelfer_containerMapEnd1234) +} + +func (x *Taint) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + var yyj7 int + var yyb7 bool + var yyhl7 bool = l >= 0 + yyj7++ + if yyhl7 { + yyb7 = yyj7 > l + } else { + yyb7 = r.CheckBreak() + } + if yyb7 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.Key = "" + } else { + x.Key = string(r.DecodeString()) + } + yyj7++ + if yyhl7 { + yyb7 = yyj7 > l + } else { + yyb7 = r.CheckBreak() + } + if yyb7 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.Value = "" + } else { + x.Value = string(r.DecodeString()) + } + yyj7++ + if yyhl7 { + yyb7 = yyj7 > l + } else { + yyb7 = r.CheckBreak() + } + if yyb7 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.Effect = "" + } else { + x.Effect = TaintEffect(r.DecodeString()) + } + for { + yyj7++ + if yyhl7 { + yyb7 = yyj7 > l + } else { + yyb7 = r.CheckBreak() + } + if yyb7 { + break + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + z.DecStructFieldNotFound(yyj7-1, "") + } + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) +} + +func (x TaintEffect) CodecEncodeSelf(e *codec1978.Encoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperEncoder(e) + _, _, _ = h, z, r + yym1 := z.EncBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.EncExt(x) { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x)) + } +} + +func (x *TaintEffect) CodecDecodeSelf(d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + yym1 := z.DecBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.DecExt(x) { + } else { + *((*string)(x)) = r.DecodeString() + } +} + +func (x *Toleration) CodecEncodeSelf(e *codec1978.Encoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperEncoder(e) + _, _, _ = h, z, r + if x == nil { + r.EncodeNil() + } else { + yym1 := z.EncBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.EncExt(x) { + } else { + yysep2 := !z.EncBinary() + yy2arr2 := z.EncBasicHandle().StructToArray + var yyq2 [4]bool + _, _, _ = yysep2, yyq2, yy2arr2 + const yyr2 bool = false + yyq2[0] = x.Key != "" + yyq2[1] = x.Operator != "" + yyq2[2] = x.Value != "" + yyq2[3] = x.Effect != "" + var yynn2 int + if yyr2 || yy2arr2 { + r.EncodeArrayStart(4) + } else { + yynn2 = 0 + for _, b := range yyq2 { + if b { + yynn2++ + } + } + r.EncodeMapStart(yynn2) + yynn2 = 0 + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[0] { + yym4 := z.EncBinary() + _ = yym4 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.Key)) + } + } else { + r.EncodeString(codecSelferC_UTF81234, "") + } + } else { + if yyq2[0] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("key")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yym5 := z.EncBinary() + _ = yym5 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.Key)) + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[1] { + x.Operator.CodecEncodeSelf(e) + } else { + r.EncodeString(codecSelferC_UTF81234, "") + } + } else { + if yyq2[1] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("operator")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + x.Operator.CodecEncodeSelf(e) + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[2] { + yym10 := z.EncBinary() + _ = yym10 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.Value)) + } + } else { + r.EncodeString(codecSelferC_UTF81234, "") + } + } else { + if yyq2[2] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("value")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yym11 := z.EncBinary() + _ = yym11 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.Value)) + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[3] { + x.Effect.CodecEncodeSelf(e) + } else { + r.EncodeString(codecSelferC_UTF81234, "") + } + } else { + if yyq2[3] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("effect")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + x.Effect.CodecEncodeSelf(e) + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayEnd1234) + } else { + z.EncSendContainerState(codecSelfer_containerMapEnd1234) + } + } + } +} + +func (x *Toleration) CodecDecodeSelf(d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + yym1 := z.DecBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.DecExt(x) { + } else { + yyct2 := r.ContainerType() + if yyct2 == codecSelferValueTypeMap1234 { + yyl2 := r.ReadMapStart() + if yyl2 == 0 { + z.DecSendContainerState(codecSelfer_containerMapEnd1234) + } else { + x.codecDecodeSelfFromMap(yyl2, d) + } + } else if yyct2 == codecSelferValueTypeArray1234 { + yyl2 := r.ReadArrayStart() + if yyl2 == 0 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + } else { + x.codecDecodeSelfFromArray(yyl2, d) + } + } else { + panic(codecSelferOnlyMapOrArrayEncodeToStructErr1234) + } + } +} + +func (x *Toleration) codecDecodeSelfFromMap(l int, d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + var yys3Slc = z.DecScratchBuffer() // default slice to decode into + _ = yys3Slc + var yyhl3 bool = l >= 0 + for yyj3 := 0; ; yyj3++ { + if yyhl3 { + if yyj3 >= l { + break + } + } else { + if r.CheckBreak() { + break + } + } + z.DecSendContainerState(codecSelfer_containerMapKey1234) + yys3Slc = r.DecodeBytes(yys3Slc, true, true) + yys3 := string(yys3Slc) + z.DecSendContainerState(codecSelfer_containerMapValue1234) + switch yys3 { + case "key": + if r.TryDecodeAsNil() { + x.Key = "" + } else { + x.Key = string(r.DecodeString()) + } + case "operator": + if r.TryDecodeAsNil() { + x.Operator = "" + } else { + x.Operator = TolerationOperator(r.DecodeString()) + } + case "value": + if r.TryDecodeAsNil() { + x.Value = "" + } else { + x.Value = string(r.DecodeString()) + } + case "effect": + if r.TryDecodeAsNil() { + x.Effect = "" + } else { + x.Effect = TaintEffect(r.DecodeString()) + } + default: + z.DecStructFieldNotFound(-1, yys3) + } // end switch yys3 + } // end for yyj3 + z.DecSendContainerState(codecSelfer_containerMapEnd1234) +} + +func (x *Toleration) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + var yyj8 int + var yyb8 bool + var yyhl8 bool = l >= 0 + yyj8++ + if yyhl8 { + yyb8 = yyj8 > l + } else { + yyb8 = r.CheckBreak() + } + if yyb8 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.Key = "" + } else { + x.Key = string(r.DecodeString()) + } + yyj8++ + if yyhl8 { + yyb8 = yyj8 > l + } else { + yyb8 = r.CheckBreak() + } + if yyb8 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.Operator = "" + } else { + x.Operator = TolerationOperator(r.DecodeString()) + } + yyj8++ + if yyhl8 { + yyb8 = yyj8 > l + } else { + yyb8 = r.CheckBreak() + } + if yyb8 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.Value = "" + } else { + x.Value = string(r.DecodeString()) + } + yyj8++ + if yyhl8 { + yyb8 = yyj8 > l + } else { + yyb8 = r.CheckBreak() + } + if yyb8 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.Effect = "" + } else { + x.Effect = TaintEffect(r.DecodeString()) + } + for { + yyj8++ + if yyhl8 { + yyb8 = yyj8 > l + } else { + yyb8 = r.CheckBreak() + } + if yyb8 { + break + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + z.DecStructFieldNotFound(yyj8-1, "") + } + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) +} + +func (x TolerationOperator) CodecEncodeSelf(e *codec1978.Encoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperEncoder(e) + _, _, _ = h, z, r + yym1 := z.EncBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.EncExt(x) { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x)) + } +} + +func (x *TolerationOperator) CodecDecodeSelf(d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + yym1 := z.DecBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.DecExt(x) { + } else { + *((*string)(x)) = r.DecodeString() + } +} + func (x *PodSpec) CodecEncodeSelf(e *codec1978.Encoder) { var h codecSelfer1234 z, r := codec1978.GenHelperEncoder(e) diff --git a/pkg/api/v1/types.go b/pkg/api/v1/types.go index d13b6dd6530..b0f379de689 100644 --- a/pkg/api/v1/types.go +++ b/pkg/api/v1/types.go @@ -1541,6 +1541,73 @@ type PreferredSchedulingTerm struct { Preference NodeSelectorTerm `json:"preference" protobuf:"bytes,2,opt,name=preference"` } +// The node this Taint is attached to has the effect "effect" on +// any pod that that does not tolerate the Taint. +type Taint struct { + // Required. The taint key to be applied to a node. + Key string `json:"key" patchStrategy:"merge" patchMergeKey:"key" protobuf:"bytes,1,opt,name=key"` + // Required. The taint value corresponding to the taint key. + Value string `json:"value,omitempty" protobuf:"bytes,2,opt,name=value"` + // Required. The effect of the taint on pods + // that do not tolerate the taint. + // Valid effects are NoSchedule and PreferNoSchedule. + Effect TaintEffect `json:"effect" protobuf:"bytes,3,opt,name=effect,casttype=TaintEffect"` +} + +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. + // Do not allow new pods to schedule onto the node unless they tolerate the taint, + // do not allow pods to start on Kubelet unless they tolerate the taint, + // but allow all already-running pods to continue running. + // Enforced by the scheduler and Kubelet. + // TaintEffectNoScheduleNoAdmit TaintEffect = "NoScheduleNoAdmit" + // NOT YET IMPLEMENTED. TODO: Uncomment field once it is implemented. + // Do not allow new pods to schedule onto the node unless they tolerate the taint, + // do not allow pods to start on Kubelet unless they tolerate the taint, + // and evict any already-running pods that do not tolerate the taint. + // Enforced by the scheduler and Kubelet. + // TaintEffectNoScheduleNoAdmitNoExecute = "NoScheduleNoAdmitNoExecute" +) + +// The pod this Toleration is attached to tolerates any taint that matches +// the triple using the matching operator . +type Toleration struct { + // Required. Key is the taint key that the toleration applies to. + Key string `json:"key,omitempty" patchStrategy:"merge" patchMergeKey:"key" protobuf:"bytes,1,opt,name=key"` + // 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. + Operator TolerationOperator `json:"operator,omitempty" protobuf:"bytes,2,opt,name=operator,casttype=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. + Value string `json:"value,omitempty" protobuf:"bytes,3,opt,name=value"` + // Effect indicates the taint effect to match. Empty means match all taint effects. + // When specified, allowed values are NoSchedule and PreferNoSchedule. + Effect TaintEffect `json:"effect,omitempty" protobuf:"bytes,4,opt,name=effect,casttype=TaintEffect"` + // TODO: For forgiveness (#1574), we'd eventually add at least a grace period + // here, and possibly an occurrence threshold and period. +} + +// 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" +) + const ( // This annotation key will be used to contain an array of v1 JSON encoded Containers // for init containers. The annotation will be placed into the internal type and cleared. diff --git a/pkg/api/v1/types_swagger_doc_generated.go b/pkg/api/v1/types_swagger_doc_generated.go index 39a60368c1a..8410b13f116 100644 --- a/pkg/api/v1/types_swagger_doc_generated.go +++ b/pkg/api/v1/types_swagger_doc_generated.go @@ -1616,6 +1616,29 @@ func (TCPSocketAction) SwaggerDoc() map[string]string { return map_TCPSocketAction } +var map_Taint = map[string]string{ + "": "The node this Taint is attached to has the effect \"effect\" on any pod that that does not tolerate the Taint.", + "key": "Required. The taint key to be applied to a node.", + "value": "Required. The taint value corresponding to the taint key.", + "effect": "Required. The effect of the taint on pods that do not tolerate the taint. Valid effects are NoSchedule and PreferNoSchedule.", +} + +func (Taint) SwaggerDoc() map[string]string { + return map_Taint +} + +var map_Toleration = map[string]string{ + "": "The pod this Toleration is attached to tolerates any taint that matches the triple using the matching operator .", + "key": "Required. Key is the taint key that the toleration applies to.", + "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.", + "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.", + "effect": "Effect indicates the taint effect to match. Empty means match all taint effects. When specified, allowed values are NoSchedule and PreferNoSchedule.", +} + +func (Toleration) SwaggerDoc() map[string]string { + return map_Toleration +} + var map_Volume = map[string]string{ "": "Volume represents a named volume in a pod that may be accessed by any container in the pod.", "name": "Volume's name. Must be a DNS_LABEL and unique within the pod. More info: http://releases.k8s.io/HEAD/docs/user-guide/identifiers.md#names", diff --git a/pkg/api/validation/validation.go b/pkg/api/validation/validation.go index 291f8d3e1ca..749ce74ac75 100644 --- a/pkg/api/validation/validation.go +++ b/pkg/api/validation/validation.go @@ -109,6 +109,10 @@ func ValidatePodSpecificAnnotations(annotations map[string]string, fldPath *fiel allErrs = append(allErrs, ValidateAffinityInPodAnnotations(annotations, fldPath)...) } + if annotations[api.TolerationsAnnotationKey] != "" { + allErrs = append(allErrs, ValidateTolerationsInPodAnnotations(annotations, fldPath)...) + } + if hostname, exists := annotations[utilpod.PodHostnameAnnotation]; exists && !validation.IsDNS1123Label(hostname) { allErrs = append(allErrs, field.Invalid(fldPath, utilpod.PodHostnameAnnotation, DNS1123LabelErrorMsg)) } @@ -1462,6 +1466,60 @@ func validateImagePullSecrets(imagePullSecrets []api.LocalObjectReference, fldPa return allErrors } +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, TaintEffectNoScheduleNoAdmitNoExecute. + case api.TaintEffectNoSchedule, api.TaintEffectPreferNoSchedule: + // case api.TaintEffectNoSchedule, api.TaintEffectPreferNoSchedule, api.TaintEffectNoScheduleNoAdmit, api.TaintEffectNoScheduleNoAdmitNoExecute: + default: + validValues := []string{ + string(api.TaintEffectNoSchedule), + string(api.TaintEffectPreferNoSchedule), + // TODO: Uncomment this block when implement TaintEffectNoScheduleNoAdmit, TaintEffectNoScheduleNoAdmitNoExecute. + // string(api.TaintEffectNoScheduleNoAdmit), + // string(api.TaintEffectNoScheduleNoAdmitNoExecute), + } + allErrors = append(allErrors, field.NotSupported(fldPath, effect, validValues)) + } + return allErrors +} + +// 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 + allErrors = append(allErrors, unversionedvalidation.ValidateLabelName(toleration.Key, idxPath.Child("key"))...) + + // validate toleration operator and value + switch toleration.Operator { + 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 + if len(toleration.Effect) > 0 { + allErrors = append(allErrors, validateTaintEffect(&toleration.Effect, true, idxPath.Child("effect"))...) + } + } + return allErrors +} + // ValidatePod tests if required fields in the pod are set. func ValidatePod(pod *api.Pod) field.ErrorList { fldPath := field.NewPath("metadata") @@ -1701,6 +1759,22 @@ func ValidateAffinityInPodAnnotations(annotations map[string]string, fldPath *fi 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 := api.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 +} + // 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{} @@ -2113,9 +2187,51 @@ func ValidateReadOnlyPersistentDisks(volumes []api.Volume, fldPath *field.Path) return allErrs } +// validateTaints tests if given taints have valid data. +func validateTaints(taints []api.Taint, fldPath *field.Path) field.ErrorList { + allErrors := field.ErrorList{} + 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"))...) + } + return allErrors +} + +// 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 := api.GetTaintsFromNodeAnnotations(annotations) + if err != nil { + allErrs = append(allErrs, field.Invalid(fldPath, api.TaintsAnnotationKey, err.Error())) + return allErrs + } + if len(taints) > 0 { + allErrs = append(allErrs, validateTaints(taints, fldPath.Child(api.TaintsAnnotationKey))...) + } + + return allErrs +} + +func ValidateNodeSpecificAnnotations(annotations map[string]string, fldPath *field.Path) field.ErrorList { + if annotations[api.TaintsAnnotationKey] != "" { + return ValidateTaintsInNodeAnnotations(annotations, fldPath) + } + return field.ErrorList{} +} + // ValidateNode tests if required fields in the node are set. func ValidateNode(node *api.Node) field.ErrorList { - allErrs := ValidateObjectMeta(&node.ObjectMeta, false, ValidateNodeName, field.NewPath("metadata")) + fldPath := field.NewPath("metadata") + allErrs := ValidateObjectMeta(&node.ObjectMeta, false, ValidateNodeName, fldPath) + allErrs = append(allErrs, ValidateNodeSpecificAnnotations(node.ObjectMeta.Annotations, fldPath.Child("annotations"))...) // Only validate spec. All status fields are optional and can be updated later. @@ -2130,7 +2246,9 @@ func ValidateNode(node *api.Node) field.ErrorList { // ValidateNodeUpdate tests to make sure a node update can be applied. Modifies oldNode. func ValidateNodeUpdate(node, oldNode *api.Node) field.ErrorList { - allErrs := ValidateObjectMetaUpdate(&node.ObjectMeta, &oldNode.ObjectMeta, field.NewPath("metadata")) + 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. diff --git a/pkg/api/validation/validation_test.go b/pkg/api/validation/validation_test.go index cd45d79c00a..472d6c04f86 100644 --- a/pkg/api/validation/validation_test.go +++ b/pkg/api/validation/validation_test.go @@ -1954,60 +1954,6 @@ func TestValidatePod(t *testing.T) { NodeName: "foobar", }, }, - } - for _, pod := range successCases { - if errs := ValidatePod(&pod); len(errs) != 0 { - t.Errorf("expected success: %v", errs) - } - } - - errorCases := map[string]api.Pod{ - "bad name": { - ObjectMeta: api.ObjectMeta{Name: "", Namespace: "ns"}, - Spec: api.PodSpec{ - RestartPolicy: api.RestartPolicyAlways, - DNSPolicy: api.DNSClusterFirst, - Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}}, - }, - }, - "bad namespace": { - ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: ""}, - Spec: api.PodSpec{ - RestartPolicy: api.RestartPolicyAlways, - DNSPolicy: api.DNSClusterFirst, - Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}}, - }, - }, - "bad spec": { - ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: "ns"}, - Spec: api.PodSpec{ - Containers: []api.Container{{}}, - }, - }, - "bad label": { - ObjectMeta: api.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"}}, - }, - }, - } - for k, v := range errorCases { - if errs := ValidatePod(&v); len(errs) == 0 { - t.Errorf("expected failure for %q", k) - } - } -} - -func TestValidateAffinity(t *testing.T) { - successCases := []api.Pod{ { // Serialized affinity requirements in annotations. ObjectMeta: api.ObjectMeta{ Name: "123", @@ -2160,6 +2106,83 @@ func TestValidateAffinity(t *testing.T) { DNSPolicy: api.DNSClusterFirst, }, }, + { // populate tolerations equal operator in annotations. + ObjectMeta: api.ObjectMeta{ + Name: "123", + Namespace: "ns", + Annotations: map[string]string{ + api.TolerationsAnnotationKey: ` + [{ + "key": "foo", + "operator": "Equal", + "value": "bar", + "effect": "NoSchedule" + }]`, + }, + }, + Spec: api.PodSpec{ + Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}}, + RestartPolicy: api.RestartPolicyAlways, + DNSPolicy: api.DNSClusterFirst, + }, + }, + { // populate tolerations exists operator in annotations. + ObjectMeta: api.ObjectMeta{ + Name: "123", + Namespace: "ns", + Annotations: map[string]string{ + api.TolerationsAnnotationKey: ` + [{ + "key": "foo", + "operator": "Exists", + "effect": "NoSchedule" + }]`, + }, + }, + Spec: api.PodSpec{ + Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}}, + RestartPolicy: api.RestartPolicyAlways, + DNSPolicy: api.DNSClusterFirst, + }, + }, + { // empty operator is ok for toleration + ObjectMeta: api.ObjectMeta{ + Name: "123", + Namespace: "ns", + Annotations: map[string]string{ + api.TolerationsAnnotationKey: ` + [{ + "key": "foo", + "value": "bar", + "effect": "NoSchedule" + }]`, + }, + }, + Spec: api.PodSpec{ + Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}}, + RestartPolicy: api.RestartPolicyAlways, + DNSPolicy: api.DNSClusterFirst, + }, + }, + { // empty efffect is ok for toleration + ObjectMeta: api.ObjectMeta{ + Name: "123", + Namespace: "ns", + Annotations: map[string]string{ + api.TolerationsAnnotationKey: ` + [{ + "key": "foo", + "operator": "Equal", + "value": "bar" + }]`, + }, + }, + Spec: api.PodSpec{ + Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}}, + RestartPolicy: api.RestartPolicyAlways, + DNSPolicy: api.DNSClusterFirst, + }, + }, } for _, pod := range successCases { if errs := ValidatePod(&pod); len(errs) != 0 { @@ -2168,6 +2191,42 @@ func TestValidateAffinity(t *testing.T) { } errorCases := map[string]api.Pod{ + "bad name": { + ObjectMeta: api.ObjectMeta{Name: "", Namespace: "ns"}, + Spec: api.PodSpec{ + RestartPolicy: api.RestartPolicyAlways, + DNSPolicy: api.DNSClusterFirst, + Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}}, + }, + }, + "bad namespace": { + ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: ""}, + Spec: api.PodSpec{ + RestartPolicy: api.RestartPolicyAlways, + DNSPolicy: api.DNSClusterFirst, + Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}}, + }, + }, + "bad spec": { + ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: "ns"}, + Spec: api.PodSpec{ + Containers: []api.Container{{}}, + }, + }, + "bad label": { + ObjectMeta: api.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"}}, + }, + }, "invalid json of node affinity in pod annotations": { ObjectMeta: api.ObjectMeta{ Name: "123", @@ -2476,6 +2535,66 @@ func TestValidateAffinity(t *testing.T) { DNSPolicy: api.DNSClusterFirst, }, }, + "invalid toleration key": { + ObjectMeta: api.ObjectMeta{ + Name: "123", + Namespace: "ns", + Annotations: map[string]string{ + api.TolerationsAnnotationKey: ` + [{ + "key": "nospecialchars^=@", + "operator": "Equal", + "value": "bar", + "effect": "NoSchedule" + }]`, + }, + }, + Spec: api.PodSpec{ + Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}}, + RestartPolicy: api.RestartPolicyAlways, + DNSPolicy: api.DNSClusterFirst, + }, + }, + "invalid toleration operator": { + ObjectMeta: api.ObjectMeta{ + Name: "123", + Namespace: "ns", + Annotations: map[string]string{ + api.TolerationsAnnotationKey: ` + [{ + "key": "foo", + "operator": "In", + "value": "bar", + "effect": "NoSchedule" + }]`, + }, + }, + Spec: api.PodSpec{ + Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}}, + RestartPolicy: api.RestartPolicyAlways, + DNSPolicy: api.DNSClusterFirst, + }, + }, + "value must be empty when `operator` is 'Exists'": { + ObjectMeta: api.ObjectMeta{ + Name: "123", + Namespace: "ns", + Annotations: map[string]string{ + api.TolerationsAnnotationKey: ` + [{ + "key": "foo", + "operator": "Exists", + "value": "bar", + "effect": "NoSchedule" + }]`, + }, + }, + Spec: api.PodSpec{ + Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}}, + RestartPolicy: api.RestartPolicyAlways, + DNSPolicy: api.DNSClusterFirst, + }, + }, } for k, v := range errorCases { if errs := ValidatePod(&v); len(errs) == 0 { @@ -3885,6 +4004,32 @@ func TestValidateNode(t *testing.T) { ExternalID: "external", }, }, + { + ObjectMeta: api.ObjectMeta{ + Name: "dedicated-node1", + // Add a valid taint to a node + Annotations: map[string]string{ + api.TaintsAnnotationKey: ` + [{ + "key": "GPU", + "value": "true", + "effect": "NoSchedule" + }]`, + }, + }, + Status: api.NodeStatus{ + Addresses: []api.NodeAddress{ + {Type: api.NodeLegacyHostIP, 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 { @@ -3936,6 +4081,119 @@ func TestValidateNode(t *testing.T) { }, }, }, + "missing-taint-key": { + + ObjectMeta: api.ObjectMeta{ + Name: "dedicated-node1", + // Add a taint with an empty key to a node + Annotations: map[string]string{ + api.TaintsAnnotationKey: ` + [{ + "key": "", + "value": "special-user-1", + "effect": "NoSchedule" + }]`, + }, + }, + Spec: api.NodeSpec{ + ExternalID: "external", + }, + }, + "bad-taint-key": { + ObjectMeta: api.ObjectMeta{ + Name: "dedicated-node1", + // Add a taint with an empty key to a node + Annotations: map[string]string{ + api.TaintsAnnotationKey: ` + [{ + "key": "NoUppercaseOrSpecialCharsLike=Equals", + "value": "special-user-1", + "effect": "NoSchedule" + }]`, + }, + }, + Spec: api.NodeSpec{ + ExternalID: "external", + }, + }, + "bad-taint-value": { + ObjectMeta: api.ObjectMeta{ + Name: "dedicated-node2", + Annotations: map[string]string{ + api.TaintsAnnotationKey: ` + [{ + "key": "dedicated", + "value": "some\\bad\\value", + "effect": "NoSchedule" + }]`, + }, + }, + Status: api.NodeStatus{ + Addresses: []api.NodeAddress{ + {Type: api.NodeLegacyHostIP, Address: "something"}, + }, + Capacity: api.ResourceList{ + api.ResourceName(api.ResourceCPU): resource.MustParse("10"), + api.ResourceName(api.ResourceMemory): resource.MustParse("0"), + }, + }, + // Add a taint with an empty value to a node + Spec: api.NodeSpec{ + ExternalID: "external", + }, + }, + "missing-taint-effect": { + ObjectMeta: api.ObjectMeta{ + Name: "dedicated-node3", + // Add a taint with an empty effect to a node + Annotations: map[string]string{ + api.TaintsAnnotationKey: ` + [{ + "key": "dedicated", + "value": "special-user-3", + "effect": "" + }]`, + }, + }, + Status: api.NodeStatus{ + Addresses: []api.NodeAddress{ + {Type: api.NodeLegacyHostIP, Address: "something"}, + }, + Capacity: api.ResourceList{ + api.ResourceName(api.ResourceCPU): resource.MustParse("10"), + api.ResourceName(api.ResourceMemory): resource.MustParse("0"), + }, + }, + Spec: api.NodeSpec{ + ExternalID: "external", + }, + }, + "invalide-taint-effect": { + ObjectMeta: api.ObjectMeta{ + Name: "dedicated-node3", + // Add a taint with an empty effect to a node + Annotations: map[string]string{ + api.TaintsAnnotationKey: ` + [{ + "key": "dedicated", + "value": "special-user-3", + "effect": "NoExecute" + }]`, + }, + }, + Status: api.NodeStatus{ + Addresses: []api.NodeAddress{ + {Type: api.NodeLegacyHostIP, 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 k, v := range errorCases { errs := ValidateNode(&v) @@ -3945,14 +4203,19 @@ func TestValidateNode(t *testing.T) { 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, + "metadata.name": true, + "metadata.labels": true, + "metadata.annotations": true, + "metadata.namespace": true, + "spec.externalID": true, + "metadata.annotations.scheduler.alpha.kubernetes.io/taints[0].key": true, + "metadata.annotations.scheduler.alpha.kubernetes.io/taints[0].value": true, + "metadata.annotations.scheduler.alpha.kubernetes.io/taints[0].effect": true, } - if expectedFields[field] == false { - t.Errorf("%s: missing prefix for: %v", k, errs[i]) + if val, ok := expectedFields[field]; ok { + if !val { + t.Errorf("%s: missing prefix for: %v", k, errs[i]) + } } } } diff --git a/pkg/kubectl/cmd/cmd.go b/pkg/kubectl/cmd/cmd.go index c0687779e20..e340479ddb0 100644 --- a/pkg/kubectl/cmd/cmd.go +++ b/pkg/kubectl/cmd/cmd.go @@ -227,6 +227,7 @@ Find more information at https://github.com/kubernetes/kubernetes.`, cmds.AddCommand(NewCmdLabel(f, out)) cmds.AddCommand(NewCmdAnnotate(f, out)) + cmds.AddCommand(NewCmdTaint(f, out)) cmds.AddCommand(cmdconfig.NewCmdConfig(clientcmd.NewDefaultPathOptions(), out)) cmds.AddCommand(NewCmdClusterInfo(f, out)) diff --git a/pkg/kubectl/cmd/taint.go b/pkg/kubectl/cmd/taint.go new file mode 100644 index 00000000000..8f85b88f65b --- /dev/null +++ b/pkg/kubectl/cmd/taint.go @@ -0,0 +1,397 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES 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 ( + "fmt" + "io" + "strings" + + "encoding/json" + "github.com/golang/glog" + "github.com/spf13/cobra" + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/meta" + cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" + "k8s.io/kubernetes/pkg/kubectl/resource" + "k8s.io/kubernetes/pkg/runtime" + utilerrors "k8s.io/kubernetes/pkg/util/errors" + "k8s.io/kubernetes/pkg/util/sets" + "k8s.io/kubernetes/pkg/util/strategicpatch" + "k8s.io/kubernetes/pkg/util/validation" +) + +// TaintOptions have the data required to perform the taint operation +type TaintOptions struct { + resources []string + taintsToAdd []api.Taint + removeTaintKeys []string + builder *resource.Builder + selector string + overwrite bool + all bool + f *cmdutil.Factory + out io.Writer + cmd *cobra.Command +} + +const ( + taint_long = `Update the taints on one or more nodes. + +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. +The value must begin with a letter or number, and may contain letters, numbers, hyphens, dots, and underscores, up to %[1]d characters. +The effect must be NoSchedule or PreferNoSchedule. +Currently taint can only apply to node.` + taint_example = `# Update node 'foo' with a taint with key 'dedicated' and value 'special-user' and effect 'NoSchedule'. +# If a taint with that key already exists, its value and effect are replaced as specified. +kubectl taint nodes foo dedicated=special-user:NoSchedule +# Remove from node 'foo' the taint with key 'dedicated' if one exists. +kubectl taint nodes foo dedicated-` +) + +func NewCmdTaint(f *cmdutil.Factory, out io.Writer) *cobra.Command { + options := &TaintOptions{} + + // retrieve a list of handled resources from printer as valid args + validArgs := []string{} + p, err := f.Printer(nil, false, false, false, false, false, false, []string{}) + cmdutil.CheckErr(err) + if p != nil { + validArgs = p.HandledResources() + } + + cmd := &cobra.Command{ + Use: "taint NODE NAME KEY_1=VAL_1:TAINT_EFFECT_1 ... KEY_N=VAL_N:TAINT_EFFECT_N", + Short: "Update the taints on one or more nodes", + Long: fmt.Sprintf(taint_long, validation.DNS1123SubdomainMaxLength, validation.LabelValueMaxLength), + Example: taint_example, + Run: func(cmd *cobra.Command, args []string) { + if err := options.Complete(f, out, cmd, args); err != nil { + cmdutil.CheckErr(err) + } + if err := options.Validate(args); err != nil { + cmdutil.CheckErr(cmdutil.UsageError(cmd, err.Error())) + } + if err := options.RunTaint(); err != nil { + cmdutil.CheckErr(err) + } + }, + ValidArgs: validArgs, + } + cmdutil.AddValidateFlags(cmd) + + cmdutil.AddPrinterFlags(cmd) + cmdutil.AddInclude3rdPartyFlags(cmd) + cmd.Flags().StringVarP(&options.selector, "selector", "l", "", "Selector (label query) to filter on") + cmd.Flags().BoolVar(&options.overwrite, "overwrite", false, "If true, allow taints to be overwritten, otherwise reject taint updates that overwrite existing taints.") + cmd.Flags().BoolVar(&options.all, "all", false, "select all nodes in the cluster") + return cmd +} + +func deleteTaintByKey(taints []api.Taint, key string) ([]api.Taint, error) { + newTaints := []api.Taint{} + found := false + for _, taint := range taints { + if taint.Key == key { + found = true + continue + } + newTaints = append(newTaints, taint) + } + + if !found { + return nil, fmt.Errorf("taint key=\"%s\" not found.", key) + } + return newTaints, nil +} + +// reorganizeTaints returns the updated set of taints, taking into account old taints that were not updated, +// old taints that were updated, old taints that were deleted, and new taints. +func reorganizeTaints(accessor meta.Object, overwrite bool, taintsToAdd []api.Taint, removeKeys []string) ([]api.Taint, error) { + newTaints := append([]api.Taint{}, taintsToAdd...) + + var oldTaints []api.Taint + var err error + annotations := accessor.GetAnnotations() + if annotations != nil { + if oldTaints, err = api.GetTaintsFromNodeAnnotations(annotations); err != nil { + return nil, err + } + } + + // add taints that already existing but not updated to newTaints + for _, oldTaint := range oldTaints { + existsInNew := false + for _, taint := range newTaints { + if taint.Key == oldTaint.Key { + existsInNew = true + break + } + } + if !existsInNew { + newTaints = append(newTaints, oldTaint) + } + } + + allErrs := []error{} + for _, taintToRemove := range removeKeys { + newTaints, err = deleteTaintByKey(newTaints, taintToRemove) + if err != nil { + allErrs = append(allErrs, err) + } + } + return newTaints, utilerrors.NewAggregate(allErrs) +} + +func parseTaints(spec []string) ([]api.Taint, []string, error) { + var taints []api.Taint + var remove []string + for _, taintSpec := range spec { + if strings.Index(taintSpec, "=") != -1 && strings.Index(taintSpec, ":") != -1 { + parts := strings.Split(taintSpec, "=") + if len(parts) != 2 || len(parts[1]) == 0 || len(validation.IsQualifiedName(parts[0])) > 0 { + return nil, nil, fmt.Errorf("invalid taint spec: %v", taintSpec) + } + + parts2 := strings.Split(parts[1], ":") + errs := validation.IsValidLabelValue(parts2[0]) + if len(parts2) != 2 || len(errs) != 0 { + return nil, nil, fmt.Errorf("invalid taint spec: %v, %s", taintSpec, strings.Join(errs, "; ")) + } + + if parts2[1] != string(api.TaintEffectNoSchedule) && parts2[1] != string(api.TaintEffectPreferNoSchedule) { + return nil, nil, fmt.Errorf("invalid taint spec: %v, unsupported taint effect", taintSpec) + } + + effect := api.TaintEffect(parts2[1]) + newTaint := api.Taint{ + Key: parts[0], + Value: parts2[0], + Effect: effect, + } + + taints = append(taints, newTaint) + } else if strings.HasSuffix(taintSpec, "-") { + remove = append(remove, taintSpec[:len(taintSpec)-1]) + } else { + return nil, nil, fmt.Errorf("unknown taint spec: %v", taintSpec) + } + } + return taints, remove, nil +} + +// Complete adapts from the command line args and factory to the data required. +func (o *TaintOptions) Complete(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string) (err error) { + namespace, _, err := f.DefaultNamespace() + if err != nil { + return err + } + + // retrieves resource and taint args from args + // also checks args to verify that all resources are specified before taints + taintArgs := []string{} + metTaintArg := false + for _, s := range args { + isTaint := strings.Contains(s, "=") || strings.HasSuffix(s, "-") + switch { + case !metTaintArg && isTaint: + metTaintArg = true + fallthrough + case metTaintArg && isTaint: + taintArgs = append(taintArgs, s) + case !metTaintArg && !isTaint: + o.resources = append(o.resources, s) + case metTaintArg && !isTaint: + return fmt.Errorf("all resources must be specified before taint changes: %s", s) + } + } + + if len(o.resources) < 1 { + return fmt.Errorf("one or more resources must be specified as ") + } + if len(taintArgs) < 1 { + return fmt.Errorf("at least one taint update is required") + } + + if o.taintsToAdd, o.removeTaintKeys, err = parseTaints(taintArgs); err != nil { + return cmdutil.UsageError(cmd, err.Error()) + } + + mapper, typer := f.Object(cmdutil.GetIncludeThirdPartyAPIs(cmd)) + o.builder = resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)). + ContinueOnError(). + NamespaceParam(namespace).DefaultNamespace() + if o.all { + o.builder = o.builder.SelectAllParam(o.all).ResourceTypes("node") + } else { + if len(o.resources) < 2 { + return fmt.Errorf("at least one resource name must be specified since 'all' parameter is not set") + } + o.builder = o.builder.ResourceNames("node", o.resources[1:]...) + } + o.builder = o.builder.SelectorParam(o.selector). + Flatten(). + Latest() + + o.f = f + o.out = out + o.cmd = cmd + + return nil +} + +// Validate checks to the TaintOptions to see if there is sufficient information run the command. +func (o TaintOptions) Validate(args []string) error { + resourceType := strings.ToLower(o.resources[0]) + if resourceType != "node" && resourceType != "nodes" { + return fmt.Errorf("invalid resource type %s, only node(s) is supported", o.resources[0]) + } + + // check the format of taint args and checks removed taints aren't in the new taints list + conflictKeys := []string{} + removeTaintKeysSet := sets.NewString(o.removeTaintKeys...) + for _, taint := range o.taintsToAdd { + if removeTaintKeysSet.Has(taint.Key) { + conflictKeys = append(conflictKeys, taint.Key) + } + } + if len(conflictKeys) > 0 { + return fmt.Errorf("can not both modify and remove the following taint(s) in the same command: %s", strings.Join(conflictKeys, ", ")) + } + + return nil +} + +// RunTaint does the work +func (o TaintOptions) RunTaint() error { + r := o.builder.Do() + if err := r.Err(); err != nil { + return err + } + + return r.Visit(func(info *resource.Info, err error) error { + if err != nil { + return err + } + + obj, err := info.Mapping.ConvertToVersion(info.Object, info.Mapping.GroupVersionKind.GroupVersion()) + if err != nil { + return err + } + name, namespace := info.Name, info.Namespace + oldData, err := json.Marshal(obj) + if err != nil { + return err + } + + if err := o.updateTaints(obj); err != nil { + return err + } + newData, err := json.Marshal(obj) + if err != nil { + return err + } + patchBytes, err := strategicpatch.CreateTwoWayMergePatch(oldData, newData, obj) + createdPatch := err == nil + if err != nil { + glog.V(2).Infof("couldn't compute patch: %v", err) + } + + mapping := info.ResourceMapping() + client, err := o.f.ClientForMapping(mapping) + if err != nil { + return err + } + helper := resource.NewHelper(client, mapping) + + var outputObj runtime.Object + if createdPatch { + outputObj, err = helper.Patch(namespace, name, api.StrategicMergePatchType, patchBytes) + } else { + outputObj, err = helper.Replace(namespace, name, false, obj) + } + if err != nil { + return err + } + + mapper, _ := o.f.Object(cmdutil.GetIncludeThirdPartyAPIs(o.cmd)) + outputFormat := cmdutil.GetFlagString(o.cmd, "output") + if outputFormat != "" { + return o.f.PrintObject(o.cmd, mapper, outputObj, o.out) + } + + cmdutil.PrintSuccess(mapper, false, o.out, info.Mapping.Resource, info.Name, "tainted") + return nil + }) +} + +// validateNoTaintOverwrites validates that when overwrite is false, to-be-updated taints don't exist in the node taint list (yet) +func validateNoTaintOverwrites(accessor meta.Object, taints []api.Taint) error { + annotations := accessor.GetAnnotations() + if annotations == nil { + return nil + } + + allErrs := []error{} + oldTaints, err := api.GetTaintsFromNodeAnnotations(annotations) + if err != nil { + allErrs = append(allErrs, err) + return utilerrors.NewAggregate(allErrs) + } + + for _, taint := range taints { + for _, oldTaint := range oldTaints { + if taint.Key == oldTaint.Key { + allErrs = append(allErrs, fmt.Errorf("Node '%s' already has a taint (%+v), and --overwrite is false", accessor.GetName(), taint)) + break + } + } + } + return utilerrors.NewAggregate(allErrs) +} + +// updateTaints updates taints of obj +func (o TaintOptions) updateTaints(obj runtime.Object) error { + accessor, err := meta.Accessor(obj) + if err != nil { + return err + } + if !o.overwrite { + if err := validateNoTaintOverwrites(accessor, o.taintsToAdd); err != nil { + return err + } + } + + annotations := accessor.GetAnnotations() + if annotations == nil { + annotations = make(map[string]string) + } + + newTaints, err := reorganizeTaints(accessor, o.overwrite, o.taintsToAdd, o.removeTaintKeys) + if err != nil { + return err + } + taintsData, err := json.Marshal(newTaints) + if err != nil { + return err + } + annotations[api.TaintsAnnotationKey] = string(taintsData) + accessor.SetAnnotations(annotations) + + return nil +} diff --git a/pkg/kubectl/cmd/taint_test.go b/pkg/kubectl/cmd/taint_test.go new file mode 100644 index 00000000000..d57b3d2d7e9 --- /dev/null +++ b/pkg/kubectl/cmd/taint_test.go @@ -0,0 +1,299 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES 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" + "encoding/json" + "io/ioutil" + "net/http" + "reflect" + "testing" + "time" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/client/unversioned/fake" + "k8s.io/kubernetes/pkg/conversion" + cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" + "k8s.io/kubernetes/pkg/runtime" +) + +func generateNodeAndTaintedNode(oldTaints []api.Taint, newTaints []api.Taint) (*api.Node, *api.Node) { + var taintedNode *api.Node + + oldTaintsData, _ := json.Marshal(oldTaints) + // Create a node. + node := &api.Node{ + ObjectMeta: api.ObjectMeta{ + Name: "node-name", + CreationTimestamp: unversioned.Time{Time: time.Now()}, + Annotations: map[string]string{ + api.TaintsAnnotationKey: string(oldTaintsData), + }, + }, + Spec: api.NodeSpec{ + ExternalID: "node-name", + }, + Status: api.NodeStatus{}, + } + clone, _ := conversion.NewCloner().DeepCopy(node) + + newTaintsData, _ := json.Marshal(newTaints) + // A copy of the same node, but tainted. + taintedNode = clone.(*api.Node) + taintedNode.Annotations = map[string]string{ + api.TaintsAnnotationKey: string(newTaintsData), + } + + return node, taintedNode +} + +func AnnotationsHaveEqualTaints(annotationA map[string]string, annotationB map[string]string) bool { + taintsA, err := api.GetTaintsFromNodeAnnotations(annotationA) + if err != nil { + return false + } + taintsB, err := api.GetTaintsFromNodeAnnotations(annotationB) + if err != nil { + return false + } + + if len(taintsA) != len(taintsB) { + return false + } + + for _, taintA := range taintsA { + found := false + for _, taintB := range taintsB { + if reflect.DeepEqual(taintA, taintB) { + found = true + break + } + } + if !found { + return false + } + } + return true +} + +func TestTaint(t *testing.T) { + tests := []struct { + description string + oldTaints []api.Taint + newTaints []api.Taint + args []string + expectFatal bool + expectTaint bool + }{ + // success cases + { + description: "taints a node with effect NoSchedule", + newTaints: []api.Taint{{ + Key: "foo", + Value: "bar", + Effect: "NoSchedule", + }}, + args: []string{"node", "node-name", "foo=bar:NoSchedule"}, + expectFatal: false, + expectTaint: true, + }, + { + description: "taints a node with effect PreferNoSchedule", + newTaints: []api.Taint{{ + Key: "foo", + Value: "bar", + Effect: "PreferNoSchedule", + }}, + args: []string{"node", "node-name", "foo=bar:PreferNoSchedule"}, + expectFatal: false, + expectTaint: true, + }, + { + description: "update an existing taint on the node, change the effect from NoSchedule to PreferNoSchedule", + oldTaints: []api.Taint{{ + Key: "foo", + Value: "bar", + Effect: "NoSchedule", + }}, + newTaints: []api.Taint{{ + Key: "foo", + Value: "bar", + Effect: "PreferNoSchedule", + }}, + args: []string{"node", "node-name", "foo=bar:PreferNoSchedule", "--overwrite"}, + expectFatal: false, + expectTaint: true, + }, + { + description: "taints a node with two taints", + newTaints: []api.Taint{{ + Key: "dedicated", + Value: "namespaceA", + Effect: "NoSchedule", + }, { + Key: "foo", + Value: "bar", + Effect: "PreferNoSchedule", + }}, + args: []string{"node", "node-name", "dedicated=namespaceA:NoSchedule", "foo=bar:PreferNoSchedule"}, + expectFatal: false, + expectTaint: true, + }, + { + description: "node has two taints, remove one of them", + oldTaints: []api.Taint{{ + Key: "dedicated", + Value: "namespaceA", + Effect: "NoSchedule", + }, { + Key: "foo", + Value: "bar", + Effect: "PreferNoSchedule", + }}, + newTaints: []api.Taint{{ + Key: "foo", + Value: "bar", + Effect: "PreferNoSchedule", + }}, + args: []string{"node", "node-name", "dedicated-"}, + expectFatal: false, + expectTaint: true, + }, + { + description: "node has two taints, update one of them and remove the other", + oldTaints: []api.Taint{{ + Key: "dedicated", + Value: "namespaceA", + Effect: "NoSchedule", + }, { + Key: "foo", + Value: "bar", + Effect: "PreferNoSchedule", + }}, + newTaints: []api.Taint{{ + Key: "foo", + Value: "bar", + Effect: "NoSchedule", + }}, + args: []string{"node", "node-name", "dedicated-", "foo=bar:NoSchedule", "--overwrite"}, + expectFatal: false, + expectTaint: true, + }, + + // error cases + { + description: "invalid taint key", + args: []string{"node", "node-name", "nospecialchars^@=banana:NoSchedule"}, + expectFatal: true, + expectTaint: false, + }, + { + description: "invalid taint effect", + args: []string{"node", "node-name", "foo=bar:NoExcute"}, + expectFatal: true, + expectTaint: false, + }, + { + description: "can't update existing taint on the node, since 'overwrite' flag is not set", + oldTaints: []api.Taint{{ + Key: "foo", + Value: "bar", + Effect: "NoSchedule", + }}, + newTaints: []api.Taint{{ + Key: "foo", + Value: "bar", + Effect: "NoSchedule", + }}, + args: []string{"node", "node-name", "foo=bar:PreferNoSchedule"}, + expectFatal: true, + expectTaint: false, + }, + } + + for _, test := range tests { + oldNode, expectNewNode := generateNodeAndTaintedNode(test.oldTaints, test.newTaints) + + new_node := &api.Node{} + tainted := false + f, tf, codec := NewAPIFactory() + + tf.Client = &fake.RESTClient{ + Codec: codec, + Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { + m := &MyReq{req} + switch { + case m.isFor("GET", "/nodes/node-name"): + return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, oldNode)}, nil + case m.isFor("PATCH", "/nodes/node-name"), m.isFor("PUT", "/nodes/node-name"): + tainted = true + data, err := ioutil.ReadAll(req.Body) + if err != nil { + t.Fatalf("%s: unexpected error: %v", test.description, err) + } + defer req.Body.Close() + if err := runtime.DecodeInto(codec, data, new_node); err != nil { + t.Fatalf("%s: unexpected error: %v", test.description, err) + } + if !AnnotationsHaveEqualTaints(expectNewNode.Annotations, new_node.Annotations) { + t.Fatalf("%s: expected:\n%v\nsaw:\n%v\n", test.description, expectNewNode.Annotations, new_node.Annotations) + } + return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, new_node)}, nil + default: + t.Fatalf("%s: unexpected request: %v %#v\n%#v", test.description, req.Method, req.URL, req) + return nil, nil + } + }), + } + tf.ClientConfig = defaultClientConfig() + + buf := bytes.NewBuffer([]byte{}) + cmd := NewCmdTaint(f, buf) + + saw_fatal := false + func() { + defer func() { + // Recover from the panic below. + _ = recover() + // Restore cmdutil behavior + cmdutil.DefaultBehaviorOnFatal() + }() + cmdutil.BehaviorOnFatal(func(e string) { saw_fatal = true; panic(e) }) + cmd.SetArgs(test.args) + cmd.Execute() + }() + + if test.expectFatal { + if !saw_fatal { + t.Fatalf("%s: unexpected non-error", test.description) + } + } + + if test.expectTaint { + if !tainted { + t.Fatalf("%s: node not tainted", test.description) + } + } + if !test.expectTaint { + if tainted { + t.Fatalf("%s: unexpected taint", test.description) + } + } + } +} diff --git a/pkg/kubectl/describe.go b/pkg/kubectl/describe.go index cf92d2cb0f4..32099b344b3 100644 --- a/pkg/kubectl/describe.go +++ b/pkg/kubectl/describe.go @@ -1617,6 +1617,7 @@ func describeNode(node *api.Node, nodeNonTerminatedPodsList *api.PodList, events return tabbedString(func(out io.Writer) error { fmt.Fprintf(out, "Name:\t%s\n", node.Name) printLabelsMultiline(out, "Labels", node.Labels) + printTaintsInAnnotationMultiline(out, "Taints", node.Annotations) fmt.Fprintf(out, "CreationTimestamp:\t%s\n", node.CreationTimestamp.Time.Format(time.RFC1123Z)) fmt.Fprintf(out, "Phase:\t%v\n", node.Status.Phase) if len(node.Status.Conditions) > 0 { @@ -2256,3 +2257,42 @@ func printLabelsMultilineWithIndent(out io.Writer, initialIndent, title, innerIn i++ } } + +// printTaintsMultiline prints multiple taints with a proper alignment. +func printTaintsInAnnotationMultiline(out io.Writer, title string, annotations map[string]string) { + taints, err := api.GetTaintsFromNodeAnnotations(annotations) + if err != nil { + taints = []api.Taint{} + } + printTaintsMultilineWithIndent(out, "", title, "\t", taints) +} + +// printTaintsMultilineWithIndent prints multiple taints with a user-defined alignment. +func printTaintsMultilineWithIndent(out io.Writer, initialIndent, title, innerIndent string, taints []api.Taint) { + fmt.Fprintf(out, "%s%s:%s", initialIndent, title, innerIndent) + + if taints == nil || len(taints) == 0 { + fmt.Fprintln(out, "") + return + } + + // to print taints in the sorted order + keys := make([]string, 0, len(taints)) + for _, taint := range taints { + keys = append(keys, taint.Key) + } + sort.Strings(keys) + + for i, key := range keys { + for _, taint := range taints { + if taint.Key == key { + if i != 0 { + fmt.Fprint(out, initialIndent) + fmt.Fprint(out, innerIndent) + } + fmt.Fprintf(out, "%s=%s:%s\n", taint.Key, taint.Value, taint.Effect) + i++ + } + } + } +} diff --git a/plugin/pkg/scheduler/algorithm/predicates/error.go b/plugin/pkg/scheduler/algorithm/predicates/error.go index b95cc4eb2f7..b32305abe65 100644 --- a/plugin/pkg/scheduler/algorithm/predicates/error.go +++ b/plugin/pkg/scheduler/algorithm/predicates/error.go @@ -32,6 +32,7 @@ var ( ErrVolumeZoneConflict = newPredicateFailureError("NoVolumeZoneConflict") ErrNodeSelectorNotMatch = newPredicateFailureError("MatchNodeSelector") ErrPodAffinityNotMatch = newPredicateFailureError("MatchInterPodAffinity") + ErrTaintsTolerationsNotMatch = newPredicateFailureError("PodToleratesNodeTaints") ErrPodNotMatchHostName = newPredicateFailureError("HostName") ErrPodNotFitsHostPorts = newPredicateFailureError("PodFitsHostPorts") ErrNodeLabelPresenceViolated = newPredicateFailureError("CheckNodeLabelPresence") diff --git a/plugin/pkg/scheduler/algorithm/predicates/predicates.go b/plugin/pkg/scheduler/algorithm/predicates/predicates.go index a01876c3fca..b880c25a68a 100644 --- a/plugin/pkg/scheduler/algorithm/predicates/predicates.go +++ b/plugin/pkg/scheduler/algorithm/predicates/predicates.go @@ -944,3 +944,58 @@ func (checker *PodAffinityChecker) NodeMatchPodAffinityAntiAffinity(pod *api.Pod } return true } + +type TolerationMatch struct { + info NodeInfo +} + +func NewTolerationMatchPredicate(info NodeInfo) algorithm.FitPredicate { + tolerationMatch := &TolerationMatch{ + info: info, + } + return tolerationMatch.PodToleratesNodeTaints +} + +func (t *TolerationMatch) PodToleratesNodeTaints(pod *api.Pod, nodeInfo *schedulercache.NodeInfo) (bool, error) { + node := nodeInfo.Node() + + taints, err := api.GetTaintsFromNodeAnnotations(node.Annotations) + if err != nil { + return false, err + } + + tolerations, err := api.GetTolerationsFromPodAnnotations(pod.Annotations) + if err != nil { + return false, err + } + + if tolerationsToleratesTaints(tolerations, taints) { + return true, nil + } + return false, ErrTaintsTolerationsNotMatch +} + +func tolerationsToleratesTaints(tolerations []api.Toleration, taints []api.Taint) bool { + // If the taint list is nil/empty, it is tolerated by all tolerations by default. + if len(taints) == 0 { + return true + } + + // The taint list isn't nil/empty, a nil/empty toleration list can't tolerate them. + if len(tolerations) == 0 { + return false + } + + for _, taint := range taints { + // skip taints that have effect PreferNoSchedule, since it is for priorities + if taint.Effect == api.TaintEffectPreferNoSchedule { + continue + } + + if !api.TaintToleratedByTolerations(taint, tolerations) { + return false + } + } + + return true +} diff --git a/plugin/pkg/scheduler/algorithm/predicates/predicates_test.go b/plugin/pkg/scheduler/algorithm/predicates/predicates_test.go index 358a483909e..8fe0cabc593 100644 --- a/plugin/pkg/scheduler/algorithm/predicates/predicates_test.go +++ b/plugin/pkg/scheduler/algorithm/predicates/predicates_test.go @@ -2358,3 +2358,286 @@ func TestInterPodAffinityWithMultipleNodes(t *testing.T) { } } } + +func TestPodToleratesTaints(t *testing.T) { + podTolerateTaintsTests := []struct { + pod *api.Pod + node api.Node + fits bool + test string + }{ + { + pod: &api.Pod{ + ObjectMeta: api.ObjectMeta{ + Name: "pod0", + }, + }, + node: api.Node{ + ObjectMeta: api.ObjectMeta{ + Annotations: map[string]string{ + api.TaintsAnnotationKey: ` + [{ + "key": "dedicated", + "value": "user1", + "effect": "NoSchedule" + }]`, + }, + }, + }, + fits: false, + test: "a pod having no tolerations can't be scheduled onto a node with nonempty taints", + }, + { + pod: &api.Pod{ + ObjectMeta: api.ObjectMeta{ + Name: "pod1", + Annotations: map[string]string{ + api.TolerationsAnnotationKey: ` + [{ + "key": "dedicated", + "value": "user1", + "effect": "NoSchedule" + }]`, + }, + }, + Spec: api.PodSpec{ + Containers: []api.Container{{Image: "pod1:V1"}}, + }, + }, + node: api.Node{ + ObjectMeta: api.ObjectMeta{ + Annotations: map[string]string{ + api.TaintsAnnotationKey: ` + [{ + "key": "dedicated", + "value": "user1", + "effect": "NoSchedule" + }]`, + }, + }, + }, + fits: true, + test: "a pod which can be scheduled on a dedicated node assgined to user1 with effect NoSchedule", + }, + { + pod: &api.Pod{ + ObjectMeta: api.ObjectMeta{ + Name: "pod2", + Annotations: map[string]string{ + api.TolerationsAnnotationKey: ` + [{ + "key": "dedicated", + "operator": "Equal", + "value": "user2", + "effect": "NoSchedule" + }]`, + }, + }, + Spec: api.PodSpec{ + Containers: []api.Container{{Image: "pod2:V1"}}, + }, + }, + node: api.Node{ + ObjectMeta: api.ObjectMeta{ + Annotations: map[string]string{ + api.TaintsAnnotationKey: ` + [{ + "key": "dedicated", + "value": "user1", + "effect": "NoSchedule" + }]`, + }, + }, + }, + fits: false, + test: "a pod which can't be scheduled on a dedicated node assgined to user2 with effect NoSchedule", + }, + { + pod: &api.Pod{ + ObjectMeta: api.ObjectMeta{ + Name: "pod2", + Annotations: map[string]string{ + api.TolerationsAnnotationKey: ` + [{ + "key": "foo", + "operator": "Exists", + "effect": "NoSchedule" + }]`, + }, + }, + Spec: api.PodSpec{ + Containers: []api.Container{{Image: "pod2:V1"}}, + }, + }, + node: api.Node{ + ObjectMeta: api.ObjectMeta{ + Annotations: map[string]string{ + api.TaintsAnnotationKey: ` + [{ + "key": "foo", + "value": "bar", + "effect": "NoSchedule" + }]`, + }, + }, + }, + fits: true, + test: "a pod can be scheduled onto the node, with a toleration uses operator Exists that tolerates the taints on the node", + }, + { + pod: &api.Pod{ + ObjectMeta: api.ObjectMeta{ + Name: "pod2", + Annotations: map[string]string{ + api.TolerationsAnnotationKey: ` + [{ + "key": "dedicated", + "operator": "Equal", + "value": "user2", + "effect": "NoSchedule" + }, { + "key": "foo", + "operator": "Exists", + "effect": "NoSchedule" + }]`, + }, + }, + Spec: api.PodSpec{ + Containers: []api.Container{{Image: "pod2:V1"}}, + }, + }, + node: api.Node{ + ObjectMeta: api.ObjectMeta{ + Annotations: map[string]string{ + api.TaintsAnnotationKey: ` + [{ + "key": "dedicated", + "value": "user2", + "effect": "NoSchedule" + }, { + "key": "foo", + "value": "bar", + "effect": "NoSchedule" + }]`, + }, + }, + }, + fits: true, + test: "a pod has multiple tolerations, node has multiple taints, all the taints are tolerated, pod can be scheduled onto the node", + }, + { + pod: &api.Pod{ + ObjectMeta: api.ObjectMeta{ + Name: "pod2", + Annotations: map[string]string{ + api.TolerationsAnnotationKey: ` + [{ + "key": "foo", + "operator": "Equal", + "value": "bar", + "effect": "PreferNoSchedule" + }]`, + }, + }, + Spec: api.PodSpec{ + Containers: []api.Container{{Image: "pod2:V1"}}, + }, + }, + node: api.Node{ + ObjectMeta: api.ObjectMeta{ + Annotations: map[string]string{ + api.TaintsAnnotationKey: ` + [{ + "key": "foo", + "value": "bar", + "effect": "NoSchedule" + }]`, + }, + }, + }, + fits: false, + test: "a pod has a toleration that keys and values match the taint on the node, but (non-empty) effect doesn't match, " + + "can't be scheduled onto the node", + }, + { + pod: &api.Pod{ + ObjectMeta: api.ObjectMeta{ + Name: "pod2", + Annotations: map[string]string{ + api.TolerationsAnnotationKey: ` + [{ + "key": "foo", + "operator": "Equal", + "value": "bar" + }]`, + }, + }, + Spec: api.PodSpec{ + Containers: []api.Container{{Image: "pod2:V1"}}, + }, + }, + node: api.Node{ + ObjectMeta: api.ObjectMeta{ + Annotations: map[string]string{ + api.TaintsAnnotationKey: ` + [{ + "key": "foo", + "value": "bar", + "effect": "NoSchedule" + }]`, + }, + }, + }, + fits: true, + test: "The pod has a toleration that keys and values match the taint on the node, the effect of toleration is empty, " + + "and the effect of taint is NoSchedule. Pod can be scheduled onto the node", + }, + { + pod: &api.Pod{ + ObjectMeta: api.ObjectMeta{ + Name: "pod2", + Annotations: map[string]string{ + api.TolerationsAnnotationKey: ` + [{ + "key": "dedicated", + "operator": "Equal", + "value": "user2", + "effect": "NoSchedule" + }]`, + }, + }, + Spec: api.PodSpec{ + Containers: []api.Container{{Image: "pod2:V1"}}, + }, + }, + node: api.Node{ + ObjectMeta: api.ObjectMeta{ + Annotations: map[string]string{ + api.TaintsAnnotationKey: ` + [{ + "key": "dedicated", + "value": "user1", + "effect": "PreferNoSchedule" + }]`, + }, + }, + }, + fits: true, + test: "The pod has a toleration that key and value don't match the taint on the node, " + + "but the effect of taint on node is PreferNochedule. Pod can be shceduled onto the node", + }, + } + + for _, test := range podTolerateTaintsTests { + tolerationMatch := TolerationMatch{FakeNodeInfo(test.node)} + nodeInfo := schedulercache.NewNodeInfo() + nodeInfo.SetNode(&test.node) + fits, err := tolerationMatch.PodToleratesNodeTaints(test.pod, nodeInfo) + if fits == false && !reflect.DeepEqual(err, ErrTaintsTolerationsNotMatch) { + t.Errorf("%s, unexpected error: %v", test.test, err) + } + if fits != test.fits { + t.Errorf("%s, expected: %v got %v", test.test, test.fits, fits) + } + } +} diff --git a/plugin/pkg/scheduler/algorithm/priorities/taint_toleration.go b/plugin/pkg/scheduler/algorithm/priorities/taint_toleration.go new file mode 100644 index 00000000000..416baeb0010 --- /dev/null +++ b/plugin/pkg/scheduler/algorithm/priorities/taint_toleration.go @@ -0,0 +1,110 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES 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/kubernetes/pkg/api" + "k8s.io/kubernetes/plugin/pkg/scheduler/algorithm" + schedulerapi "k8s.io/kubernetes/plugin/pkg/scheduler/api" + "k8s.io/kubernetes/plugin/pkg/scheduler/schedulercache" +) + +// NodeTaints hold the node lister +type TaintToleration struct { + nodeLister algorithm.NodeLister +} + +// NewTaintTolerationPriority +func NewTaintTolerationPriority(nodeLister algorithm.NodeLister) algorithm.PriorityFunction { + taintToleration := &TaintToleration{ + nodeLister: nodeLister, + } + return taintToleration.ComputeTaintTolerationPriority +} + +// CountIntolerableTaintsPreferNoSchedule gives the count of intolerable taints of a pod with effect PreferNoSchedule +func countIntolerableTaintsPreferNoSchedule(taints []api.Taint, tolerations []api.Toleration) (intolerableTaints int) { + for _, taint := range taints { + // check only on taints that have effect PreferNoSchedule + if taint.Effect != api.TaintEffectPreferNoSchedule { + continue + } + + if !api.TaintToleratedByTolerations(taint, tolerations) { + intolerableTaints++ + } + } + return +} + +// getAllTolerationEffectPreferNoSchedule gets the list of all Toleration with Effect PreferNoSchedule +func getAllTolerationPreferNoSchedule(tolerations []api.Toleration) (tolerationList []api.Toleration) { + for _, toleration := range tolerations { + if len(toleration.Effect) == 0 || toleration.Effect == api.TaintEffectPreferNoSchedule { + tolerationList = append(tolerationList, toleration) + } + } + return +} + +// ComputeTaintTolerationPriority prepares the priority list for all the nodes based on the number of intolerable taints on the node +func (s *TaintToleration) ComputeTaintTolerationPriority(pod *api.Pod, nodeNameToInfo map[string]*schedulercache.NodeInfo, nodeLister algorithm.NodeLister) (schedulerapi.HostPriorityList, error) { + // counts hold the count of intolerable taints of a pod for a given node + counts := make(map[string]int) + + // the max value of counts + var maxCount int + + nodes, err := nodeLister.List() + if err != nil { + return nil, err + } + + tolerations, err := api.GetTolerationsFromPodAnnotations(pod.Annotations) + if err != nil { + return nil, err + } + // Fetch a list of all toleration with effect PreferNoSchedule + tolerationList := getAllTolerationPreferNoSchedule(tolerations) + + // calculate the intolerable taints for all the nodes + for _, node := range nodes.Items { + taints, err := api.GetTaintsFromNodeAnnotations(node.Annotations) + if err != nil { + return nil, err + } + + count := countIntolerableTaintsPreferNoSchedule(taints, tolerationList) + counts[node.Name] = count + if count > maxCount { + maxCount = count + } + } + + // The maximum priority value to give to a node + // Priority values range from 0 - maxPriority + const maxPriority = 10 + result := []schedulerapi.HostPriority{} + for _, node := range nodes.Items { + fScore := float64(maxPriority) + if maxCount > 0 { + fScore = (1.0 - float64(counts[node.Name])/float64(maxCount)) * 10 + } + result = append(result, schedulerapi.HostPriority{Host: node.Name, Score: int(fScore)}) + } + return result, nil +} diff --git a/plugin/pkg/scheduler/algorithm/priorities/taint_toleration_test.go b/plugin/pkg/scheduler/algorithm/priorities/taint_toleration_test.go new file mode 100644 index 00000000000..7c2fbbbbcb8 --- /dev/null +++ b/plugin/pkg/scheduler/algorithm/priorities/taint_toleration_test.go @@ -0,0 +1,229 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES 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 ( + "encoding/json" + "reflect" + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/plugin/pkg/scheduler/algorithm" + schedulerapi "k8s.io/kubernetes/plugin/pkg/scheduler/api" + "k8s.io/kubernetes/plugin/pkg/scheduler/schedulercache" +) + +func nodeWithTaints(nodeName string, taints []api.Taint) api.Node { + taintsData, _ := json.Marshal(taints) + return api.Node{ + ObjectMeta: api.ObjectMeta{ + Name: nodeName, + Annotations: map[string]string{ + api.TaintsAnnotationKey: string(taintsData), + }, + }, + } +} + +func podWithTolerations(tolerations []api.Toleration) *api.Pod { + tolerationData, _ := json.Marshal(tolerations) + return &api.Pod{ + ObjectMeta: api.ObjectMeta{ + Annotations: map[string]string{ + api.TolerationsAnnotationKey: string(tolerationData), + }, + }, + } +} + +// This function will create a set of nodes and pods and test the priority +// Nodes with zero,one,two,three,four and hundred taints are created +// Pods with zero,one,two,three,four and hundred tolerations are created + +func TestTaintAndToleration(t *testing.T) { + tests := []struct { + pod *api.Pod + nodes []api.Node + expectedList schedulerapi.HostPriorityList + test string + }{ + // basic test case + { + test: "node with taints tolerated by the pod, gets a higher score than those node with intolerable taints", + pod: podWithTolerations([]api.Toleration{{ + Key: "foo", + Operator: api.TolerationOpEqual, + Value: "bar", + Effect: api.TaintEffectPreferNoSchedule, + }}), + nodes: []api.Node{ + nodeWithTaints("nodeA", []api.Taint{{ + Key: "foo", + Value: "bar", + Effect: api.TaintEffectPreferNoSchedule, + }}), + nodeWithTaints("nodeB", []api.Taint{{ + Key: "foo", + Value: "blah", + Effect: api.TaintEffectPreferNoSchedule, + }}), + }, + expectedList: []schedulerapi.HostPriority{ + {Host: "nodeA", Score: 10}, + {Host: "nodeB", Score: 0}, + }, + }, + // the count of taints that are tolerated by pod, does not matter. + { + test: "the nodes that all of their taints are tolerated by the pod, get the same score, no matter how many tolerable taints a node has", + pod: podWithTolerations([]api.Toleration{ + { + Key: "cpu-type", + Operator: api.TolerationOpEqual, + Value: "arm64", + Effect: api.TaintEffectPreferNoSchedule, + }, { + Key: "disk-type", + Operator: api.TolerationOpEqual, + Value: "ssd", + Effect: api.TaintEffectPreferNoSchedule, + }, + }), + nodes: []api.Node{ + nodeWithTaints("nodeA", []api.Taint{}), + nodeWithTaints("nodeB", []api.Taint{ + { + Key: "cpu-type", + Value: "arm64", + Effect: api.TaintEffectPreferNoSchedule, + }, + }), + nodeWithTaints("nodeC", []api.Taint{ + { + Key: "cpu-type", + Value: "arm64", + Effect: api.TaintEffectPreferNoSchedule, + }, { + Key: "disk-type", + Value: "ssd", + Effect: api.TaintEffectPreferNoSchedule, + }, + }), + }, + expectedList: []schedulerapi.HostPriority{ + {Host: "nodeA", Score: 10}, + {Host: "nodeB", Score: 10}, + {Host: "nodeC", Score: 10}, + }, + }, + // the count of taints on a node that are not tolerated by pod, matters. + { + test: "the more intolerable taints a node has, the lower score it gets.", + pod: podWithTolerations([]api.Toleration{{ + Key: "foo", + Operator: api.TolerationOpEqual, + Value: "bar", + Effect: api.TaintEffectPreferNoSchedule, + }}), + nodes: []api.Node{ + nodeWithTaints("nodeA", []api.Taint{}), + nodeWithTaints("nodeB", []api.Taint{ + { + Key: "cpu-type", + Value: "arm64", + Effect: api.TaintEffectPreferNoSchedule, + }, + }), + nodeWithTaints("nodeC", []api.Taint{ + { + Key: "cpu-type", + Value: "arm64", + Effect: api.TaintEffectPreferNoSchedule, + }, { + Key: "disk-type", + Value: "ssd", + Effect: api.TaintEffectPreferNoSchedule, + }, + }), + }, + expectedList: []schedulerapi.HostPriority{ + {Host: "nodeA", Score: 10}, + {Host: "nodeB", Score: 5}, + {Host: "nodeC", Score: 0}, + }, + }, + // taints-tolerations priority only takes care about the taints and tolerations that have effect PreferNoSchedule + { + test: "only taints and tolerations that have effect PreferNoSchedule are checked by taints-tolerations priority function", + pod: podWithTolerations([]api.Toleration{ + { + Key: "cpu-type", + Operator: api.TolerationOpEqual, + Value: "arm64", + Effect: api.TaintEffectNoSchedule, + }, { + Key: "disk-type", + Operator: api.TolerationOpEqual, + Value: "ssd", + Effect: api.TaintEffectNoSchedule, + }, + }), + nodes: []api.Node{ + nodeWithTaints("nodeA", []api.Taint{}), + nodeWithTaints("nodeB", []api.Taint{ + { + Key: "cpu-type", + Value: "arm64", + Effect: api.TaintEffectNoSchedule, + }, + }), + nodeWithTaints("nodeC", []api.Taint{ + { + Key: "cpu-type", + Value: "arm64", + Effect: api.TaintEffectPreferNoSchedule, + }, { + Key: "disk-type", + Value: "ssd", + Effect: api.TaintEffectPreferNoSchedule, + }, + }), + }, + expectedList: []schedulerapi.HostPriority{ + {Host: "nodeA", Score: 10}, + {Host: "nodeB", Score: 10}, + {Host: "nodeC", Score: 0}, + }, + }, + } + for _, test := range tests { + nodeNameToInfo := schedulercache.CreateNodeNameToInfoMap([]*api.Pod{{}}) + taintToleration := TaintToleration{nodeLister: algorithm.FakeNodeLister(api.NodeList{Items: test.nodes})} + list, err := taintToleration.ComputeTaintTolerationPriority( + test.pod, + nodeNameToInfo, + algorithm.FakeNodeLister(api.NodeList{Items: test.nodes})) + if err != nil { + t.Errorf("%s, unexpected error: %v", test.test, err) + } + + if !reflect.DeepEqual(test.expectedList, list) { + t.Errorf("%s,\nexpected:\n\t%+v,\ngot:\n\t%+v", test.test, test.expectedList, list) + } + } + +} diff --git a/plugin/pkg/scheduler/algorithmprovider/defaults/defaults.go b/plugin/pkg/scheduler/algorithmprovider/defaults/defaults.go index b9304cc7bc4..16c57660906 100644 --- a/plugin/pkg/scheduler/algorithmprovider/defaults/defaults.go +++ b/plugin/pkg/scheduler/algorithmprovider/defaults/defaults.go @@ -145,6 +145,14 @@ func defaultPredicates() sets.String { // 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 based on whether a pod can tolerate all of the node's taints + factory.RegisterFitPredicateFactory( + "PodToleratesNodeTaints", + func(args factory.PluginFactoryArgs) algorithm.FitPredicate { + return predicates.NewTolerationMatchPredicate(args.NodeInfo) + }, + ), ) } @@ -173,5 +181,14 @@ func defaultPriorities() sets.String { Weight: 1, }, ), + factory.RegisterPriorityConfigFactory( + "TaintTolerationPriority", + factory.PriorityConfigFactory{ + Function: func(args factory.PluginFactoryArgs) algorithm.PriorityFunction { + return priorities.NewTaintTolerationPriority(args.NodeLister) + }, + Weight: 1, + }, + ), ) } diff --git a/test/e2e/kubectl.go b/test/e2e/kubectl.go index bf17c257e96..de022ff75e6 100644 --- a/test/e2e/kubectl.go +++ b/test/e2e/kubectl.go @@ -47,6 +47,7 @@ import ( "k8s.io/kubernetes/pkg/controller" "k8s.io/kubernetes/pkg/kubectl/cmd/util" "k8s.io/kubernetes/pkg/labels" + pkgutil "k8s.io/kubernetes/pkg/util" utilnet "k8s.io/kubernetes/pkg/util/net" "k8s.io/kubernetes/pkg/util/wait" "k8s.io/kubernetes/pkg/version" @@ -1283,6 +1284,39 @@ var _ = framework.KubeDescribe("Kubectl client", func() { } }) }) + + framework.KubeDescribe("Kubectl taint", func() { + It("should update the taint on a node", func() { + taintName := fmt.Sprintf("kubernetes.io/e2e-taint-key-%s", string(pkgutil.NewUUID())) + taintValue := "testing-taint-value" + taintEffect := fmt.Sprintf("%s", api.TaintEffectNoSchedule) + + nodes, err := c.Nodes().List(api.ListOptions{}) + Expect(err).NotTo(HaveOccurred()) + node := nodes.Items[0] + nodeName := node.Name + + By("adding the taint " + taintName + " with value " + taintValue + " and taint effect " + taintEffect + " to a node") + framework.RunKubectlOrDie("taint", "nodes", nodeName, taintName+"="+taintValue+":"+taintEffect) + By("verifying the node has the taint " + taintName + " with the value " + taintValue) + output := framework.RunKubectlOrDie("describe", "node", nodeName) + requiredStrings := [][]string{ + {"Name:", nodeName}, + {"Taints:"}, + {taintName + "=" + taintValue + ":" + taintEffect}, + } + checkOutput(output, requiredStrings) + + By("removing the taint " + taintName + " of a node") + framework.RunKubectlOrDie("taint", "nodes", nodeName, taintName+"-") + By("verifying the node doesn't have the taint " + taintName) + output = framework.RunKubectlOrDie("describe", "node", nodeName) + if strings.Contains(output, taintName) { + framework.Failf("Failed removing taint " + taintName + " of the node " + nodeName) + } + + }) + }) }) // Checks whether the output split by line contains the required elements. diff --git a/test/e2e/scheduler_predicates.go b/test/e2e/scheduler_predicates.go index dfb430660f5..fbb4d051620 100644 --- a/test/e2e/scheduler_predicates.go +++ b/test/e2e/scheduler_predicates.go @@ -19,6 +19,7 @@ package e2e import ( "fmt" "path/filepath" + "strings" "time" "k8s.io/kubernetes/pkg/api" @@ -1280,4 +1281,250 @@ var _ = framework.KubeDescribe("SchedulerPredicates [Serial]", func() { framework.ExpectNoError(err) Expect(labelPod.Spec.NodeName).To(Equal(nodeName)) }) + + // 1. Run a pod to get an available node, then delete the pod + // 2. Taint the node with a random taint + // 3. Try to relaunch the pod with tolerations tolerate the taints on node, + // and the pod's nodeName specified to the name of node found in step 1 + It("validates that taints-tolerations is respected if matching", func() { + // launch a pod to find a node which can launch a pod. We intentionally do + // not just take the node list and choose the first of them. Depending on the + // cluster and the scheduler it might be that a "normal" pod cannot be + // scheduled onto it. + By("Trying to launch a pod without a toleration to get a node which can launch it.") + podName := "without-toleration" + _, err := c.Pods(ns).Create(&api.Pod{ + TypeMeta: unversioned.TypeMeta{ + Kind: "Pod", + }, + ObjectMeta: api.ObjectMeta{ + Name: podName, + }, + Spec: api.PodSpec{ + Containers: []api.Container{ + { + Name: podName, + Image: "gcr.io/google_containers/pause-amd64:3.0", + }, + }, + }, + }) + framework.ExpectNoError(err) + framework.ExpectNoError(framework.WaitForPodRunningInNamespace(c, podName, ns)) + pod, err := c.Pods(ns).Get(podName) + framework.ExpectNoError(err) + + nodeName := pod.Spec.NodeName + err = c.Pods(ns).Delete(podName, api.NewDeleteOptions(0)) + framework.ExpectNoError(err) + + By("Trying to apply a random taint on the found node.") + taintName := fmt.Sprintf("kubernetes.io/e2e-taint-key-%s", string(util.NewUUID())) + taintValue := "testing-taint-value" + taintEffect := string(api.TaintEffectNoSchedule) + framework.RunKubectlOrDie("taint", "nodes", nodeName, taintName+"="+taintValue+":"+taintEffect) + By("verifying the node has the taint " + taintName + " with the value " + taintValue) + output := framework.RunKubectlOrDie("describe", "node", nodeName) + requiredStrings := [][]string{ + {"Name:", nodeName}, + {"Taints:"}, + {taintName, taintValue, taintEffect}, + } + checkOutput(output, requiredStrings) + + By("Trying to apply a random label on the found node.") + labelKey := fmt.Sprintf("kubernetes.io/e2e-label-key-%s", string(util.NewUUID())) + labelValue := "testing-label-value" + framework.RunKubectlOrDie("label", "nodes", nodeName, labelKey+"="+labelValue) + By("verifying the node has the label " + labelKey + " with the value " + labelValue) + labelOutput := framework.RunKubectlOrDie("describe", "node", nodeName) + labelOutputRequiredStrings := [][]string{ + {"Name:", nodeName}, + {"Labels:"}, + {labelKey + "=" + labelValue}, + } + checkOutput(labelOutput, labelOutputRequiredStrings) + + By("Trying to relaunch the pod, now with tolerations.") + tolerationPodName := "with-tolerations" + _, err = c.Pods(ns).Create(&api.Pod{ + TypeMeta: unversioned.TypeMeta{ + Kind: "Pod", + }, + ObjectMeta: api.ObjectMeta{ + Name: tolerationPodName, + Annotations: map[string]string{ + "scheduler.alpha.kubernetes.io/tolerations": ` + [ + { + "key": "` + taintName + `", + "value": "` + taintValue + `", + "effect": "` + taintEffect + `" + } + ]`, + }, + }, + Spec: api.PodSpec{ + NodeSelector: map[string]string{labelKey: labelValue}, + Containers: []api.Container{ + { + Name: tolerationPodName, + Image: "gcr.io/google_containers/pause-amd64:3.0", + }, + }, + }, + }) + framework.ExpectNoError(err) + defer c.Pods(ns).Delete(tolerationPodName, api.NewDeleteOptions(0)) + + // check that pod got scheduled. We intentionally DO NOT check that the + // pod is running because this will create a race condition with the + // kubelet and the scheduler: the scheduler might have scheduled a pod + // already when the kubelet does not know about its new taint yet. The + // kubelet will then refuse to launch the pod. + framework.ExpectNoError(framework.WaitForPodNotPending(c, ns, tolerationPodName)) + deployedPod, err := c.Pods(ns).Get(tolerationPodName) + framework.ExpectNoError(err) + Expect(deployedPod.Spec.NodeName).To(Equal(nodeName)) + + By("removing the taint " + taintName + " off the node " + nodeName) + framework.RunKubectlOrDie("taint", "nodes", nodeName, taintName+"-") + By("verifying the node doesn't have the taint " + taintName) + output = framework.RunKubectlOrDie("describe", "node", nodeName) + if strings.Contains(output, taintName) { + framework.Failf("Failed removing taint " + taintName + " of the node " + nodeName) + } + + By("removing the label " + labelKey + " off the node " + nodeName) + framework.RunKubectlOrDie("label", "nodes", nodeName, labelKey+"-") + By("verifying the node doesn't have the label " + labelKey) + output = framework.RunKubectlOrDie("describe", "node", nodeName) + if strings.Contains(output, labelKey) { + framework.Failf("Failed removing label " + labelKey + " of the node " + nodeName) + } + }) + + // 1. Run a pod to get an available node, then delete the pod + // 2. Taint the node with a random taint + // 3. Try to relaunch the pod still no tolerations, + // and the pod's nodeName specified to the name of node found in step 1 + It("validates that taints-tolerations is respected if not matching", func() { + // launch a pod to find a node which can launch a pod. We intentionally do + // not just take the node list and choose the first of them. Depending on the + // cluster and the scheduler it might be that a "normal" pod cannot be + // scheduled onto it. + By("Trying to launch a pod without a toleration to get a node which can launch it.") + podName := "without-toleration" + _, err := c.Pods(ns).Create(&api.Pod{ + TypeMeta: unversioned.TypeMeta{ + Kind: "Pod", + }, + ObjectMeta: api.ObjectMeta{ + Name: podName, + }, + Spec: api.PodSpec{ + Containers: []api.Container{ + { + Name: podName, + Image: "gcr.io/google_containers/pause-amd64:3.0", + }, + }, + }, + }) + framework.ExpectNoError(err) + framework.ExpectNoError(framework.WaitForPodRunningInNamespace(c, podName, ns)) + pod, err := c.Pods(ns).Get(podName) + framework.ExpectNoError(err) + + nodeName := pod.Spec.NodeName + err = c.Pods(ns).Delete(podName, api.NewDeleteOptions(0)) + framework.ExpectNoError(err) + + By("Trying to apply a random taint on the found node.") + taintName := fmt.Sprintf("kubernetes.io/e2e-taint-key-%s", string(util.NewUUID())) + taintValue := "testing-taint-value" + taintEffect := string(api.TaintEffectNoSchedule) + framework.RunKubectlOrDie("taint", "nodes", nodeName, taintName+"="+taintValue+":"+taintEffect) + By("verifying the node has the taint " + taintName + " with the value " + taintValue) + output := framework.RunKubectlOrDie("describe", "node", nodeName) + requiredStrings := [][]string{ + {"Name:", nodeName}, + {"Taints:"}, + {taintName, taintValue, taintEffect}, + } + checkOutput(output, requiredStrings) + + By("Trying to apply a random label on the found node.") + labelKey := fmt.Sprintf("kubernetes.io/e2e-label-key-%s", string(util.NewUUID())) + labelValue := "testing-label-value" + framework.RunKubectlOrDie("label", "nodes", nodeName, labelKey+"="+labelValue) + By("verifying the node has the label " + labelKey + " with the value " + labelValue) + labelOutput := framework.RunKubectlOrDie("describe", "node", nodeName) + labelOutputRequiredStrings := [][]string{ + {"Name:", nodeName}, + {"Labels:"}, + {labelKey + "=" + labelValue}, + } + checkOutput(labelOutput, labelOutputRequiredStrings) + + By("Trying to relaunch the pod, still no tolerations.") + podNameNoTolerations := "still-no-tolerations" + podNoTolerations := api.Pod{ + TypeMeta: unversioned.TypeMeta{ + Kind: "Pod", + }, + ObjectMeta: api.ObjectMeta{ + Name: podNameNoTolerations, + }, + Spec: api.PodSpec{ + NodeSelector: map[string]string{labelKey: labelValue}, + Containers: []api.Container{ + { + Name: podNameNoTolerations, + Image: "gcr.io/google_containers/pause-amd64:3.0", + }, + }, + }, + } + _, err = c.Pods(ns).Create(&podNoTolerations) + framework.ExpectNoError(err) + // Wait a bit to allow scheduler to do its thing + // TODO: this is brittle; there's no guarantee the scheduler will have run in 10 seconds. + framework.Logf("Sleeping 10 seconds and crossing our fingers that scheduler will run in that time.") + time.Sleep(10 * time.Second) + + verifyResult(c, podNameNoTolerations, ns) + cleanupPods(c, ns) + + By("removing the taint " + taintName + " off the node " + nodeName) + framework.RunKubectlOrDie("taint", "nodes", nodeName, taintName+"-") + By("verifying the node doesn't have the taint " + taintName) + output = framework.RunKubectlOrDie("describe", "node", nodeName) + if strings.Contains(output, taintName) { + framework.Failf("Failed removing taint " + taintName + " of the node " + nodeName) + } + + By("Trying to relaunch the same.") + _, err = c.Pods(ns).Create(&podNoTolerations) + framework.ExpectNoError(err) + defer c.Pods(ns).Delete(podNameNoTolerations, api.NewDeleteOptions(0)) + + // check that pod got scheduled. We intentionally DO NOT check that the + // pod is running because this will create a race condition with the + // kubelet and the scheduler: the scheduler might have scheduled a pod + // already when the kubelet does not know about its new taint yet. The + // kubelet will then refuse to launch the pod. + framework.ExpectNoError(framework.WaitForPodNotPending(c, ns, podNameNoTolerations)) + deployedPod, err := c.Pods(ns).Get(podNameNoTolerations) + framework.ExpectNoError(err) + Expect(deployedPod.Spec.NodeName).To(Equal(nodeName)) + + By("removing the label " + labelKey + " off the node " + nodeName) + framework.RunKubectlOrDie("label", "nodes", nodeName, labelKey+"-") + By("verifying the node doesn't have the label " + labelKey) + output = framework.RunKubectlOrDie("describe", "node", nodeName) + if strings.Contains(output, labelKey) { + framework.Failf("Failed removing label " + labelKey + " of the node " + nodeName) + } + }) })