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
[]()
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 @@
+
+
+
+
+
+
+
+
+
+
+
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
+
+
+[]()
+
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)
+ }
+ })
})