diff --git a/.generated_docs b/.generated_docs
index 1623371318..c9054bdb9a 100644
--- a/.generated_docs
+++ b/.generated_docs
@@ -20,6 +20,7 @@ docs/man/man1/kubectl-config-use-context.1
docs/man/man1/kubectl-config-view.1
docs/man/man1/kubectl-config.1
docs/man/man1/kubectl-convert.1
+docs/man/man1/kubectl-cordon.1
docs/man/man1/kubectl-create-namespace.1
docs/man/man1/kubectl-create-secret-docker-registry.1
docs/man/man1/kubectl-create-secret-generic.1
@@ -27,6 +28,7 @@ docs/man/man1/kubectl-create-secret.1
docs/man/man1/kubectl-create.1
docs/man/man1/kubectl-delete.1
docs/man/man1/kubectl-describe.1
+docs/man/man1/kubectl-drain.1
docs/man/man1/kubectl-edit.1
docs/man/man1/kubectl-exec.1
docs/man/man1/kubectl-explain.1
@@ -43,6 +45,7 @@ docs/man/man1/kubectl-rolling-update.1
docs/man/man1/kubectl-run.1
docs/man/man1/kubectl-scale.1
docs/man/man1/kubectl-stop.1
+docs/man/man1/kubectl-uncordon.1
docs/man/man1/kubectl-version.1
docs/man/man1/kubectl.1
docs/user-guide/kubectl/kubectl.md
@@ -61,6 +64,7 @@ docs/user-guide/kubectl/kubectl_config_unset.md
docs/user-guide/kubectl/kubectl_config_use-context.md
docs/user-guide/kubectl/kubectl_config_view.md
docs/user-guide/kubectl/kubectl_convert.md
+docs/user-guide/kubectl/kubectl_cordon.md
docs/user-guide/kubectl/kubectl_create.md
docs/user-guide/kubectl/kubectl_create_namespace.md
docs/user-guide/kubectl/kubectl_create_secret.md
@@ -68,6 +72,7 @@ docs/user-guide/kubectl/kubectl_create_secret_docker-registry.md
docs/user-guide/kubectl/kubectl_create_secret_generic.md
docs/user-guide/kubectl/kubectl_delete.md
docs/user-guide/kubectl/kubectl_describe.md
+docs/user-guide/kubectl/kubectl_drain.md
docs/user-guide/kubectl/kubectl_edit.md
docs/user-guide/kubectl/kubectl_exec.md
docs/user-guide/kubectl/kubectl_explain.md
@@ -83,4 +88,5 @@ docs/user-guide/kubectl/kubectl_replace.md
docs/user-guide/kubectl/kubectl_rolling-update.md
docs/user-guide/kubectl/kubectl_run.md
docs/user-guide/kubectl/kubectl_scale.md
+docs/user-guide/kubectl/kubectl_uncordon.md
docs/user-guide/kubectl/kubectl_version.md
diff --git a/build/common.sh b/build/common.sh
index 54d9866e23..5839df403f 100755
--- a/build/common.sh
+++ b/build/common.sh
@@ -448,6 +448,7 @@ function kube::build::source_targets() {
test
third_party
contrib/completions/bash/kubectl
+ contrib/mesos
.generated_docs
)
if [ -n "${KUBERNETES_CONTRIB:-}" ]; then
diff --git a/contrib/completions/bash/kubectl b/contrib/completions/bash/kubectl
index e7380383f0..bf378a5e03 100644
--- a/contrib/completions/bash/kubectl
+++ b/contrib/completions/bash/kubectl
@@ -1140,6 +1140,125 @@ _kubectl_scale()
must_have_one_noun=()
}
+_kubectl_cordon()
+{
+ last_command="kubectl_cordon"
+ commands=()
+
+ flags=()
+ two_word_flags=()
+ flags_with_completion=()
+ flags_completion=()
+
+ flags+=("--alsologtostderr")
+ flags+=("--api-version=")
+ 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+=("--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=()
+}
+
+_kubectl_drain()
+{
+ last_command="kubectl_drain"
+ commands=()
+
+ flags=()
+ two_word_flags=()
+ flags_with_completion=()
+ flags_completion=()
+
+ flags+=("--force")
+ flags+=("--grace-period=")
+ flags+=("--alsologtostderr")
+ flags+=("--api-version=")
+ 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+=("--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=()
+}
+
+_kubectl_uncordon()
+{
+ last_command="kubectl_uncordon"
+ commands=()
+
+ flags=()
+ two_word_flags=()
+ flags_with_completion=()
+ flags_completion=()
+
+ flags+=("--alsologtostderr")
+ flags+=("--api-version=")
+ 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+=("--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=()
+}
+
_kubectl_attach()
{
last_command="kubectl_attach"
@@ -2241,6 +2360,9 @@ _kubectl()
commands+=("logs")
commands+=("rolling-update")
commands+=("scale")
+ commands+=("cordon")
+ commands+=("drain")
+ commands+=("uncordon")
commands+=("attach")
commands+=("exec")
commands+=("port-forward")
diff --git a/docs/man/man1/kubectl-cordon.1 b/docs/man/man1/kubectl-cordon.1
new file mode 100644
index 0000000000..989692bebf
--- /dev/null
+++ b/docs/man/man1/kubectl-cordon.1
@@ -0,0 +1,133 @@
+.TH "KUBERNETES" "1" " kubernetes User Manuals" "Eric Paris" "Jan 2015" ""
+
+
+.SH NAME
+.PP
+kubectl cordon \- Mark node as unschedulable
+
+
+.SH SYNOPSIS
+.PP
+\fBkubectl cordon\fP [OPTIONS]
+
+
+.SH DESCRIPTION
+.PP
+Mark node as unschedulable.
+
+
+.SH OPTIONS INHERITED FROM PARENT COMMANDS
+.PP
+\fB\-\-alsologtostderr\fP=false
+ log to standard error as well as files
+
+.PP
+\fB\-\-api\-version\fP=""
+ The API version to use when talking to the server
+
+.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
+# Mark node "foo" as unschedulable.
+$ kubectl cordon foo
+
+
+.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-drain.1 b/docs/man/man1/kubectl-drain.1
new file mode 100644
index 0000000000..826071dc47
--- /dev/null
+++ b/docs/man/man1/kubectl-drain.1
@@ -0,0 +1,157 @@
+.TH "KUBERNETES" "1" " kubernetes User Manuals" "Eric Paris" "Jan 2015" ""
+
+
+.SH NAME
+.PP
+kubectl drain \- Drain node in preparation for maintenance
+
+
+.SH SYNOPSIS
+.PP
+\fBkubectl drain\fP [OPTIONS]
+
+
+.SH DESCRIPTION
+.PP
+Drain node in preparation for maintenance.
+
+.PP
+The given node will be marked unschedulable to prevent new pods from arriving.
+Then drain deletes all pods except mirror pods (which cannot be deleted through
+the API server). If there are any pods that are neither mirror pods nor
+managed by a ReplicationController or DaemonSet, then drain will not delete any
+pods unless you use \-\-force.
+
+.PP
+When you are ready to put the node back into service, use kubectl uncordon, which
+will make the node schedulable again.
+
+
+.SH OPTIONS
+.PP
+\fB\-\-force\fP=false
+ Continue even if there are pods not managed by a ReplicationController or DaemonSet.
+
+.PP
+\fB\-\-grace\-period\fP=\-1
+ Period of time in seconds given to each pod to terminate gracefully. If negative, the default value specified in the pod will be used.
+
+
+.SH OPTIONS INHERITED FROM PARENT COMMANDS
+.PP
+\fB\-\-alsologtostderr\fP=false
+ log to standard error as well as files
+
+.PP
+\fB\-\-api\-version\fP=""
+ The API version to use when talking to the server
+
+.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
+# Drain node "foo", even if there are pods not managed by a ReplicationController or DaemonSet on it.
+$ kubectl drain foo \-\-force
+
+# As above, but abort if there are pods not managed by a ReplicationController or DaemonSet, and use a grace period of 15 minutes.
+$ kubectl drain foo \-\-grace\-period=900
+
+
+.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-uncordon.1 b/docs/man/man1/kubectl-uncordon.1
new file mode 100644
index 0000000000..62a9b85542
--- /dev/null
+++ b/docs/man/man1/kubectl-uncordon.1
@@ -0,0 +1,133 @@
+.TH "KUBERNETES" "1" " kubernetes User Manuals" "Eric Paris" "Jan 2015" ""
+
+
+.SH NAME
+.PP
+kubectl uncordon \- Mark node as schedulable
+
+
+.SH SYNOPSIS
+.PP
+\fBkubectl uncordon\fP [OPTIONS]
+
+
+.SH DESCRIPTION
+.PP
+Mark node as schedulable.
+
+
+.SH OPTIONS INHERITED FROM PARENT COMMANDS
+.PP
+\fB\-\-alsologtostderr\fP=false
+ log to standard error as well as files
+
+.PP
+\fB\-\-api\-version\fP=""
+ The API version to use when talking to the server
+
+.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
+# Mark node "foo" as schedulable.
+$ kubectl uncordon foo
+
+
+.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 d3065b675b..ff48459302 100644
--- a/docs/man/man1/kubectl.1
+++ b/docs/man/man1/kubectl.1
@@ -116,7 +116,7 @@ Find more information at
.SH SEE ALSO
.PP
-\fBkubectl\-get(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\-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\-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\-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\-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,
.SH HISTORY
diff --git a/docs/user-guide/kubectl/kubectl.md b/docs/user-guide/kubectl/kubectl.md
index f5235805a5..a4fd0c3037 100644
--- a/docs/user-guide/kubectl/kubectl.md
+++ b/docs/user-guide/kubectl/kubectl.md
@@ -85,9 +85,11 @@ kubectl
* [kubectl cluster-info](kubectl_cluster-info.md) - Display cluster info
* [kubectl config](kubectl_config.md) - config modifies kubeconfig files
* [kubectl convert](kubectl_convert.md) - Convert config files between different API versions
+* [kubectl cordon](kubectl_cordon.md) - Mark node as unschedulable
* [kubectl create](kubectl_create.md) - Create a resource by filename or stdin
* [kubectl delete](kubectl_delete.md) - Delete resources by filenames, stdin, resources and names, or by resources and label selector.
* [kubectl describe](kubectl_describe.md) - Show details of a specific resource or group of resources
+* [kubectl drain](kubectl_drain.md) - Drain node in preparation for maintenance
* [kubectl edit](kubectl_edit.md) - Edit a resource on the server
* [kubectl exec](kubectl_exec.md) - Execute a command in a container.
* [kubectl explain](kubectl_explain.md) - Documentation of resources.
@@ -103,9 +105,10 @@ kubectl
* [kubectl rolling-update](kubectl_rolling-update.md) - Perform a rolling update of the given ReplicationController.
* [kubectl run](kubectl_run.md) - Run a particular image on the cluster.
* [kubectl scale](kubectl_scale.md) - Set a new size for a Replication Controller, Job, or Deployment.
+* [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 8-Dec-2015
+###### Auto generated by spf13/cobra on 4-Jan-2016
[![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/docs/user-guide/kubectl/kubectl.md?pixel)]()
diff --git a/docs/user-guide/kubectl/kubectl_cordon.md b/docs/user-guide/kubectl/kubectl_cordon.md
new file mode 100644
index 0000000000..cb4be4db8c
--- /dev/null
+++ b/docs/user-guide/kubectl/kubectl_cordon.md
@@ -0,0 +1,88 @@
+
+
+
+
+
+
+
+
+
+
+
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 cordon
+
+Mark node as unschedulable
+
+### Synopsis
+
+
+Mark node as unschedulable.
+
+
+```
+kubectl cordon NODE
+```
+
+### Examples
+
+```
+# Mark node "foo" as unschedulable.
+$ kubectl cordon foo
+
+```
+
+### Options inherited from parent commands
+
+```
+ --alsologtostderr[=false]: log to standard error as well as files
+ --api-version="": The API version to use when talking to the server
+ --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 4-Jan-2016
+
+
+[![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/docs/user-guide/kubectl/kubectl_cordon.md?pixel)]()
+
diff --git a/docs/user-guide/kubectl/kubectl_drain.md b/docs/user-guide/kubectl/kubectl_drain.md
new file mode 100644
index 0000000000..214d4298fd
--- /dev/null
+++ b/docs/user-guide/kubectl/kubectl_drain.md
@@ -0,0 +1,107 @@
+
+
+
+
+
+
+
+
+
+
+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 drain
+
+Drain node in preparation for maintenance
+
+### Synopsis
+
+
+Drain node in preparation for maintenance.
+
+The given node will be marked unschedulable to prevent new pods from arriving.
+Then drain deletes all pods except mirror pods (which cannot be deleted through
+the API server). If there are any pods that are neither mirror pods nor
+managed by a ReplicationController or DaemonSet, then drain will not delete any
+pods unless you use --force.
+
+When you are ready to put the node back into service, use kubectl uncordon, which
+will make the node schedulable again.
+
+
+```
+kubectl drain NODE
+```
+
+### Examples
+
+```
+# Drain node "foo", even if there are pods not managed by a ReplicationController or DaemonSet on it.
+$ kubectl drain foo --force
+
+# As above, but abort if there are pods not managed by a ReplicationController or DaemonSet, and use a grace period of 15 minutes.
+$ kubectl drain foo --grace-period=900
+
+```
+
+### Options
+
+```
+ --force[=false]: Continue even if there are pods not managed by a ReplicationController or DaemonSet.
+ --grace-period=-1: Period of time in seconds given to each pod to terminate gracefully. If negative, the default value specified in the pod will be used.
+```
+
+### Options inherited from parent commands
+
+```
+ --alsologtostderr[=false]: log to standard error as well as files
+ --api-version="": The API version to use when talking to the server
+ --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 6-Jan-2016
+
+
+[![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/docs/user-guide/kubectl/kubectl_drain.md?pixel)]()
+
diff --git a/docs/user-guide/kubectl/kubectl_uncordon.md b/docs/user-guide/kubectl/kubectl_uncordon.md
new file mode 100644
index 0000000000..e1490a8efd
--- /dev/null
+++ b/docs/user-guide/kubectl/kubectl_uncordon.md
@@ -0,0 +1,88 @@
+
+
+
+
+
+
+
+
+
+
+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 uncordon
+
+Mark node as schedulable
+
+### Synopsis
+
+
+Mark node as schedulable.
+
+
+```
+kubectl uncordon NODE
+```
+
+### Examples
+
+```
+# Mark node "foo" as schedulable.
+$ kubectl uncordon foo
+
+```
+
+### Options inherited from parent commands
+
+```
+ --alsologtostderr[=false]: log to standard error as well as files
+ --api-version="": The API version to use when talking to the server
+ --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 4-Jan-2016
+
+
+[![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/docs/user-guide/kubectl/kubectl_uncordon.md?pixel)]()
+
diff --git a/pkg/kubectl/cmd/cmd.go b/pkg/kubectl/cmd/cmd.go
index 0a800ffaae..506d5e65a7 100644
--- a/pkg/kubectl/cmd/cmd.go
+++ b/pkg/kubectl/cmd/cmd.go
@@ -162,6 +162,9 @@ Find more information at https://github.com/kubernetes/kubernetes.`,
cmds.AddCommand(NewCmdLogs(f, out))
cmds.AddCommand(NewCmdRollingUpdate(f, out))
cmds.AddCommand(NewCmdScale(f, out))
+ cmds.AddCommand(NewCmdCordon(f, out))
+ cmds.AddCommand(NewCmdDrain(f, out))
+ cmds.AddCommand(NewCmdUncordon(f, out))
cmds.AddCommand(NewCmdAttach(f, in, out, err))
cmds.AddCommand(NewCmdExec(f, in, out, err))
diff --git a/pkg/kubectl/cmd/cmd_test.go b/pkg/kubectl/cmd/cmd_test.go
index 189b402f6f..2fff13e9b9 100644
--- a/pkg/kubectl/cmd/cmd_test.go
+++ b/pkg/kubectl/cmd/cmd_test.go
@@ -232,6 +232,7 @@ func NewAPIFactory() (*cmdutil.Factory, *testFactory, runtime.Codec) {
fakeClient := t.Client.(*fake.RESTClient)
c := client.NewOrDie(t.ClientConfig)
c.Client = fakeClient.Client
+ c.ExtensionsClient.Client = fakeClient.Client
return c, t.Err
},
RESTClient: func(*meta.RESTMapping) (resource.RESTClient, error) {
diff --git a/pkg/kubectl/cmd/drain.go b/pkg/kubectl/cmd/drain.go
new file mode 100644
index 0000000000..db09620e1e
--- /dev/null
+++ b/pkg/kubectl/cmd/drain.go
@@ -0,0 +1,314 @@
+/*
+Copyright 2015 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"
+ "reflect"
+ "strings"
+
+ "github.com/spf13/cobra"
+
+ "k8s.io/kubernetes/pkg/api"
+ "k8s.io/kubernetes/pkg/api/meta"
+ "k8s.io/kubernetes/pkg/controller"
+ // "k8s.io/kubernetes/pkg/api/unversioned"
+ client "k8s.io/kubernetes/pkg/client/unversioned"
+ "k8s.io/kubernetes/pkg/fields"
+ cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
+ "k8s.io/kubernetes/pkg/kubectl/resource"
+ "k8s.io/kubernetes/pkg/kubelet/types"
+ "k8s.io/kubernetes/pkg/runtime"
+)
+
+type DrainOptions struct {
+ client *client.Client
+ factory *cmdutil.Factory
+ Force bool
+ GracePeriodSeconds int
+ mapper meta.RESTMapper
+ nodeInfo *resource.Info
+ out io.Writer
+ typer runtime.ObjectTyper
+}
+
+const (
+ cordon_long = `Mark node as unschedulable.
+`
+ cordon_example = `# Mark node "foo" as unschedulable.
+$ kubectl cordon foo
+`
+)
+
+func NewCmdCordon(f *cmdutil.Factory, out io.Writer) *cobra.Command {
+ options := &DrainOptions{factory: f, out: out}
+
+ return &cobra.Command{
+ Use: "cordon NODE",
+ Short: "Mark node as unschedulable",
+ Long: cordon_long,
+ Example: cordon_example,
+ Run: func(cmd *cobra.Command, args []string) {
+ cmdutil.CheckErr(options.SetupDrain(cmd, args))
+ cmdutil.CheckErr(options.RunCordonOrUncordon(true))
+ },
+ }
+}
+
+const (
+ uncordon_long = `Mark node as schedulable.
+`
+ uncordon_example = `# Mark node "foo" as schedulable.
+$ kubectl uncordon foo
+`
+)
+
+func NewCmdUncordon(f *cmdutil.Factory, out io.Writer) *cobra.Command {
+ options := &DrainOptions{factory: f, out: out}
+
+ return &cobra.Command{
+ Use: "uncordon NODE",
+ Short: "Mark node as schedulable",
+ Long: uncordon_long,
+ Example: uncordon_example,
+ Run: func(cmd *cobra.Command, args []string) {
+ cmdutil.CheckErr(options.SetupDrain(cmd, args))
+ cmdutil.CheckErr(options.RunCordonOrUncordon(false))
+ },
+ }
+}
+
+const (
+ drain_long = `Drain node in preparation for maintenance.
+
+The given node will be marked unschedulable to prevent new pods from arriving.
+Then drain deletes all pods except mirror pods (which cannot be deleted through
+the API server). If there are any pods that are neither mirror pods nor
+managed by a ReplicationController or DaemonSet, then drain will not delete any
+pods unless you use --force.
+
+When you are ready to put the node back into service, use kubectl uncordon, which
+will make the node schedulable again.
+`
+ drain_example = `# Drain node "foo", even if there are pods not managed by a ReplicationController or DaemonSet on it.
+$ kubectl drain foo --force
+
+# As above, but abort if there are pods not managed by a ReplicationController or DaemonSet, and use a grace period of 15 minutes.
+$ kubectl drain foo --grace-period=900
+`
+)
+
+func NewCmdDrain(f *cmdutil.Factory, out io.Writer) *cobra.Command {
+ options := &DrainOptions{factory: f, out: out}
+
+ cmd := &cobra.Command{
+ Use: "drain NODE",
+ Short: "Drain node in preparation for maintenance",
+ Long: drain_long,
+ Example: drain_example,
+ Run: func(cmd *cobra.Command, args []string) {
+ cmdutil.CheckErr(options.SetupDrain(cmd, args))
+ cmdutil.CheckErr(options.RunDrain())
+ },
+ }
+ cmd.Flags().BoolVar(&options.Force, "force", false, "Continue even if there are pods not managed by a ReplicationController or DaemonSet.")
+ cmd.Flags().IntVar(&options.GracePeriodSeconds, "grace-period", -1, "Period of time in seconds given to each pod to terminate gracefully. If negative, the default value specified in the pod will be used.")
+ return cmd
+}
+
+// SetupDrain populates some fields from the factory, grabs command line
+// arguments and looks up the node using Builder
+func (o *DrainOptions) SetupDrain(cmd *cobra.Command, args []string) error {
+ var err error
+ if len(args) != 1 {
+ return cmdutil.UsageError(cmd, fmt.Sprintf("USAGE: %s [flags]", cmd.Use))
+ }
+
+ if o.client, err = o.factory.Client(); err != nil {
+ return err
+ }
+
+ o.mapper, o.typer = o.factory.Object()
+
+ cmdNamespace, _, err := o.factory.DefaultNamespace()
+ if err != nil {
+ return err
+ }
+
+ r := o.factory.NewBuilder().
+ NamespaceParam(cmdNamespace).DefaultNamespace().
+ ResourceNames("node", args[0]).
+ Do()
+
+ if err = r.Err(); err != nil {
+ return err
+ }
+
+ return r.Visit(func(info *resource.Info, err error) error {
+ if err != nil {
+ return err
+ }
+ o.nodeInfo = info
+ return nil
+ })
+}
+
+// RunDrain runs the 'drain' command
+func (o *DrainOptions) RunDrain() error {
+ if err := o.RunCordonOrUncordon(true); err != nil {
+ return err
+ }
+
+ pods, err := o.getPodsForDeletion()
+ if err != nil {
+ return err
+ }
+
+ if err = o.deletePods(pods); err != nil {
+ return err
+ }
+ cmdutil.PrintSuccess(o.mapper, false, o.out, "node", o.nodeInfo.Name, "drained")
+ return nil
+}
+
+// getPodsForDeletion returns all the pods we're going to delete. If there are
+// any unmanaged pods and the user didn't pass --force, we return that list in
+// an error.
+func (o *DrainOptions) getPodsForDeletion() ([]api.Pod, error) {
+ pods := []api.Pod{}
+ podList, err := o.client.Pods(api.NamespaceAll).List(api.ListOptions{FieldSelector: fields.SelectorFromSet(fields.Set{"spec.nodeName": o.nodeInfo.Name})})
+ if err != nil {
+ return pods, err
+ }
+ unreplicatedPodNames := []string{}
+
+ for _, pod := range podList.Items {
+ _, found := pod.ObjectMeta.Annotations[types.ConfigMirrorAnnotationKey]
+ if found {
+ // Skip mirror pod
+ continue
+ }
+ replicated := false
+
+ creatorRef, found := pod.ObjectMeta.Annotations[controller.CreatedByAnnotation]
+ if found {
+ // Now verify that the specified creator actually exists.
+ var sr api.SerializedReference
+ err := api.Scheme.DecodeInto([]byte(creatorRef), &sr)
+ if err != nil {
+ return pods, err
+ }
+ if sr.Reference.Kind == "ReplicationController" {
+ rc, err := o.client.ReplicationControllers(sr.Reference.Namespace).Get(sr.Reference.Name)
+ // Assume the only reason for an error is because the RC is
+ // gone/missing, not for any other cause. TODO(mml): something more
+ // sophisticated than this
+ if err == nil && rc != nil {
+ replicated = true
+ }
+ } else if sr.Reference.Kind == "DaemonSet" {
+ ds, err := o.client.DaemonSets(sr.Reference.Namespace).Get(sr.Reference.Name)
+
+ // Assume the only reason for an error is because the DaemonSet is
+ // gone/missing, not for any other cause. TODO(mml): something more
+ // sophisticated than this
+ if err == nil && ds != nil {
+ replicated = true
+ }
+ }
+ }
+ if replicated || o.Force {
+ pods = append(pods, pod)
+ }
+ if !replicated {
+ unreplicatedPodNames = append(unreplicatedPodNames, pod.Name)
+ }
+ }
+
+ if len(unreplicatedPodNames) > 0 {
+ joined := strings.Join(unreplicatedPodNames, ", ")
+ if !o.Force {
+ return pods, fmt.Errorf("refusing to continue due to pods managed by neither a ReplicationController nor a DaemonSet: %s (use --force to override)", joined)
+ }
+ fmt.Fprintf(o.out, "WARNING: About to delete these pods managed by neither a ReplicationController nor a DaemonSet: %s\n", joined)
+ }
+ return pods, nil
+}
+
+// deletePods deletes the pods on the api server
+func (o *DrainOptions) deletePods(pods []api.Pod) error {
+ deleteOptions := api.DeleteOptions{}
+ if o.GracePeriodSeconds >= 0 {
+ gracePeriodSeconds := int64(o.GracePeriodSeconds)
+ deleteOptions.GracePeriodSeconds = &gracePeriodSeconds
+ }
+
+ for _, pod := range pods {
+ err := o.client.Pods(pod.Namespace).Delete(pod.Name, &deleteOptions)
+ if err != nil {
+ return err
+ }
+ cmdutil.PrintSuccess(o.mapper, false, o.out, "pod", pod.Name, "deleted")
+ }
+
+ return nil
+}
+
+// RunCordonOrUncordon runs either Cordon or Uncordon. The desired value for
+// "Unschedulable" is passed as the first arg.
+func (o *DrainOptions) RunCordonOrUncordon(desired bool) error {
+ cmdNamespace, _, err := o.factory.DefaultNamespace()
+ if err != nil {
+ return err
+ }
+
+ if o.nodeInfo.Mapping.GroupVersionKind.Kind == "Node" {
+ unsched := reflect.ValueOf(o.nodeInfo.Object).Elem().FieldByName("Spec").FieldByName("Unschedulable")
+ if unsched.Bool() == desired {
+ cmdutil.PrintSuccess(o.mapper, false, o.out, o.nodeInfo.Mapping.Resource, o.nodeInfo.Name, already(desired))
+ } else {
+ helper := resource.NewHelper(o.client, o.nodeInfo.Mapping)
+ unsched.SetBool(desired)
+ _, err := helper.Replace(cmdNamespace, o.nodeInfo.Name, true, o.nodeInfo.Object)
+ if err != nil {
+ return err
+ }
+ cmdutil.PrintSuccess(o.mapper, false, o.out, o.nodeInfo.Mapping.Resource, o.nodeInfo.Name, changed(desired))
+ }
+ } else {
+ cmdutil.PrintSuccess(o.mapper, false, o.out, o.nodeInfo.Mapping.Resource, o.nodeInfo.Name, "skipped")
+ }
+
+ return nil
+}
+
+// already() and changed() return suitable strings for {un,}cordoning
+
+func already(desired bool) string {
+ if desired {
+ return "already cordoned"
+ }
+ return "already uncordoned"
+}
+
+func changed(desired bool) string {
+ if desired {
+ return "cordoned"
+ }
+ return "uncordoned"
+}
diff --git a/pkg/kubectl/cmd/drain_test.go b/pkg/kubectl/cmd/drain_test.go
new file mode 100644
index 0000000000..e4daed8ae9
--- /dev/null
+++ b/pkg/kubectl/cmd/drain_test.go
@@ -0,0 +1,450 @@
+/*
+Copyright 2015 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"
+ "io"
+ "io/ioutil"
+ "net/http"
+ "net/url"
+ "os"
+ "reflect"
+ "strings"
+ "testing"
+ "time"
+
+ "github.com/spf13/cobra"
+
+ "k8s.io/kubernetes/pkg/api"
+ "k8s.io/kubernetes/pkg/api/testapi"
+ "k8s.io/kubernetes/pkg/api/unversioned"
+ "k8s.io/kubernetes/pkg/apis/extensions"
+ client "k8s.io/kubernetes/pkg/client/unversioned"
+ "k8s.io/kubernetes/pkg/client/unversioned/fake"
+ "k8s.io/kubernetes/pkg/controller"
+ "k8s.io/kubernetes/pkg/conversion"
+ cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
+ "k8s.io/kubernetes/pkg/runtime"
+)
+
+var node *api.Node
+var cordoned_node *api.Node
+
+func TestMain(m *testing.M) {
+ // Create a node.
+ node = &api.Node{
+ ObjectMeta: api.ObjectMeta{
+ Name: "node",
+ CreationTimestamp: unversioned.Time{time.Now()},
+ },
+ Spec: api.NodeSpec{
+ ExternalID: "node",
+ },
+ Status: api.NodeStatus{},
+ }
+ clone, _ := conversion.NewCloner().DeepCopy(node)
+
+ // A copy of the same node, but cordoned.
+ cordoned_node = clone.(*api.Node)
+ cordoned_node.Spec.Unschedulable = true
+ os.Exit(m.Run())
+}
+
+func TestCordon(t *testing.T) {
+ tests := []struct {
+ description string
+ node *api.Node
+ expected *api.Node
+ cmd func(*cmdutil.Factory, io.Writer) *cobra.Command
+ arg string
+ expectFatal bool
+ }{
+ {
+ description: "node/node syntax",
+ node: cordoned_node,
+ expected: node,
+ cmd: NewCmdUncordon,
+ arg: "node/node",
+ expectFatal: false,
+ },
+ {
+ description: "uncordon for real",
+ node: cordoned_node,
+ expected: node,
+ cmd: NewCmdUncordon,
+ arg: "node",
+ expectFatal: false,
+ },
+ {
+ description: "uncordon does nothing",
+ node: node,
+ expected: node,
+ cmd: NewCmdUncordon,
+ arg: "node",
+ expectFatal: false,
+ },
+ {
+ description: "cordon does nothing",
+ node: cordoned_node,
+ expected: cordoned_node,
+ cmd: NewCmdCordon,
+ arg: "node",
+ expectFatal: false,
+ },
+ {
+ description: "cordon for real",
+ node: node,
+ expected: cordoned_node,
+ cmd: NewCmdCordon,
+ arg: "node",
+ expectFatal: false,
+ },
+ {
+ description: "cordon missing node",
+ node: node,
+ expected: node,
+ cmd: NewCmdCordon,
+ arg: "bar",
+ expectFatal: true,
+ },
+ {
+ description: "uncordon missing node",
+ node: node,
+ expected: node,
+ cmd: NewCmdUncordon,
+ arg: "bar",
+ expectFatal: true,
+ },
+ }
+
+ for _, test := range tests {
+ f, tf, codec := NewAPIFactory()
+ new_node := &api.Node{}
+ updated := false
+ 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"):
+ return &http.Response{StatusCode: 200, Body: objBody(codec, test.node)}, nil
+ case m.isFor("GET", "/nodes/bar"):
+ return &http.Response{StatusCode: 404, Body: stringBody("nope")}, nil
+ case m.isFor("PUT", "/nodes/node"):
+ 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 !reflect.DeepEqual(test.expected.Spec, new_node.Spec) {
+ t.Fatalf("%s: expected:\n%v\nsaw:\n%v\n", test.description, test.expected.Spec, new_node.Spec)
+ }
+ updated = true
+ return &http.Response{StatusCode: 200, 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 = &client.Config{GroupVersion: testapi.Default.GroupVersion()}
+
+ buf := bytes.NewBuffer([]byte{})
+ cmd := test.cmd(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([]string{test.arg})
+ cmd.Execute()
+ }()
+
+ if test.expectFatal {
+ if !saw_fatal {
+ t.Fatalf("%s: unexpected non-error", test.description)
+ }
+ if updated {
+ t.Fatalf("%s: unexpcted update", test.description)
+ }
+ }
+
+ if !test.expectFatal && saw_fatal {
+ t.Fatalf("%s: unexpected error", test.description)
+ }
+ if !reflect.DeepEqual(test.expected.Spec, test.node.Spec) && !updated {
+ t.Fatalf("%s: node never updated", test.description)
+ }
+ }
+}
+
+func TestDrain(t *testing.T) {
+ labels := make(map[string]string)
+ labels["my_key"] = "my_value"
+
+ rc := api.ReplicationController{
+ ObjectMeta: api.ObjectMeta{
+ Name: "rc",
+ Namespace: "default",
+ CreationTimestamp: unversioned.Time{time.Now()},
+ Labels: labels,
+ SelfLink: testapi.Default.SelfLink("replicationcontrollers", "rc"),
+ },
+ Spec: api.ReplicationControllerSpec{
+ Selector: labels,
+ },
+ }
+
+ rc_anno := make(map[string]string)
+ rc_anno[controller.CreatedByAnnotation] = refJson(t, &rc)
+
+ replicated_pod := api.Pod{
+ ObjectMeta: api.ObjectMeta{
+ Name: "bar",
+ Namespace: "default",
+ CreationTimestamp: unversioned.Time{time.Now()},
+ Labels: labels,
+ Annotations: rc_anno,
+ },
+ Spec: api.PodSpec{
+ NodeName: "node",
+ },
+ }
+
+ ds := extensions.DaemonSet{
+ ObjectMeta: api.ObjectMeta{
+ Name: "ds",
+ Namespace: "default",
+ CreationTimestamp: unversioned.Time{time.Now()},
+ SelfLink: "/apis/extensions/v1beta1/namespaces/default/daemonsets/ds",
+ },
+ Spec: extensions.DaemonSetSpec{
+ Selector: &extensions.LabelSelector{MatchLabels: labels},
+ },
+ }
+
+ ds_anno := make(map[string]string)
+ ds_anno[controller.CreatedByAnnotation] = refJson(t, &ds)
+
+ ds_pod := api.Pod{
+ ObjectMeta: api.ObjectMeta{
+ Name: "bar",
+ Namespace: "default",
+ CreationTimestamp: unversioned.Time{time.Now()},
+ Labels: labels,
+ Annotations: ds_anno,
+ },
+ Spec: api.PodSpec{
+ NodeName: "node",
+ },
+ }
+
+ naked_pod := api.Pod{
+ ObjectMeta: api.ObjectMeta{
+ Name: "bar",
+ Namespace: "default",
+ CreationTimestamp: unversioned.Time{time.Now()},
+ Labels: labels,
+ },
+ Spec: api.PodSpec{
+ NodeName: "node",
+ },
+ }
+
+ tests := []struct {
+ description string
+ node *api.Node
+ expected *api.Node
+ pods []api.Pod
+ rcs []api.ReplicationController
+ args []string
+ expectFatal bool
+ expectDelete bool
+ }{
+ {
+ description: "RC-managed pod",
+ node: node,
+ expected: cordoned_node,
+ pods: []api.Pod{replicated_pod},
+ rcs: []api.ReplicationController{rc},
+ args: []string{"node"},
+ expectFatal: false,
+ expectDelete: true,
+ },
+ {
+ description: "DS-managed pod",
+ node: node,
+ expected: cordoned_node,
+ pods: []api.Pod{ds_pod},
+ rcs: []api.ReplicationController{rc},
+ args: []string{"node"},
+ expectFatal: false,
+ expectDelete: true,
+ },
+ {
+ description: "naked pod",
+ node: node,
+ expected: cordoned_node,
+ pods: []api.Pod{naked_pod},
+ rcs: []api.ReplicationController{},
+ args: []string{"node"},
+ expectFatal: true,
+ expectDelete: false,
+ },
+ {
+ description: "naked pod with --force",
+ node: node,
+ expected: cordoned_node,
+ pods: []api.Pod{naked_pod},
+ rcs: []api.ReplicationController{},
+ args: []string{"node", "--force"},
+ expectFatal: false,
+ expectDelete: true,
+ },
+ {
+ description: "empty node",
+ node: node,
+ expected: cordoned_node,
+ pods: []api.Pod{},
+ rcs: []api.ReplicationController{rc},
+ args: []string{"node"},
+ expectFatal: false,
+ expectDelete: false,
+ },
+ }
+
+ for _, test := range tests {
+ new_node := &api.Node{}
+ deleted := 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"):
+ return &http.Response{StatusCode: 200, Body: objBody(codec, test.node)}, nil
+ case m.isFor("GET", "/namespaces/default/replicationcontrollers/rc"):
+ return &http.Response{StatusCode: 200, Body: objBody(codec, &test.rcs[0])}, nil
+ case m.isFor("GET", "/namespaces/default/daemonsets/ds"):
+ return &http.Response{StatusCode: 200, Body: objBody(testapi.Extensions.Codec(), &ds)}, nil
+ case m.isFor("GET", "/pods"):
+ values, err := url.ParseQuery(req.URL.RawQuery)
+ if err != nil {
+ t.Fatalf("%s: unexpected error: %v", test.description, err)
+ }
+ get_params := make(url.Values)
+ get_params["fieldSelector"] = []string{"spec.nodeName=node"}
+ if !reflect.DeepEqual(get_params, values) {
+ t.Fatalf("%s: expected:\n%v\nsaw:\n%v\n", test.description, get_params, values)
+ }
+ return &http.Response{StatusCode: 200, Body: objBody(codec, &api.PodList{Items: test.pods})}, nil
+ case m.isFor("GET", "/replicationcontrollers"):
+ return &http.Response{StatusCode: 200, Body: objBody(codec, &api.ReplicationControllerList{Items: test.rcs})}, nil
+ case m.isFor("PUT", "/nodes/node"):
+ 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 !reflect.DeepEqual(test.expected.Spec, new_node.Spec) {
+ t.Fatalf("%s: expected:\n%v\nsaw:\n%v\n", test.description, test.expected.Spec, new_node.Spec)
+ }
+ return &http.Response{StatusCode: 200, Body: objBody(codec, new_node)}, nil
+ case m.isFor("DELETE", "/namespaces/default/pods/bar"):
+ deleted = true
+ return &http.Response{StatusCode: 204, Body: objBody(codec, &test.pods[0])}, nil
+ default:
+ t.Fatalf("%s: unexpected request: %v %#v\n%#v", test.description, req.Method, req.URL, req)
+ return nil, nil
+ }
+ }),
+ }
+ tf.ClientConfig = &client.Config{GroupVersion: testapi.Default.GroupVersion()}
+
+ buf := bytes.NewBuffer([]byte{})
+ cmd := NewCmdDrain(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.expectDelete {
+ if !deleted {
+ t.Fatalf("%s: pod never deleted", test.description)
+ }
+ }
+ if !test.expectDelete {
+ if deleted {
+ t.Fatalf("%s: unexpected delete", test.description)
+ }
+ }
+ }
+}
+
+type MyReq struct {
+ Request *http.Request
+}
+
+func (m *MyReq) isFor(method string, path string) bool {
+ req := m.Request
+
+ return method == req.Method && (req.URL.Path == path || req.URL.Path == strings.Join([]string{"/api/v1", path}, "") || req.URL.Path == strings.Join([]string{"/apis/extensions/v1beta1", path}, ""))
+}
+
+func refJson(t *testing.T, o runtime.Object) string {
+ ref, err := api.GetReference(o)
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+
+ _, _, codec := NewAPIFactory()
+ json, err := codec.Encode(&api.SerializedReference{Reference: *ref})
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+
+ return string(json)
+}
diff --git a/pkg/kubectl/cmd/util/factory.go b/pkg/kubectl/cmd/util/factory.go
index 333b72a75d..dda5fc1da3 100644
--- a/pkg/kubectl/cmd/util/factory.go
+++ b/pkg/kubectl/cmd/util/factory.go
@@ -673,3 +673,10 @@ func (f *Factory) NilClientMapperForCommand() resource.ClientMapper {
return nil, nil
})
}
+
+// One stop shopping for a Builder
+func (f *Factory) NewBuilder() *resource.Builder {
+ mapper, typer := f.Object()
+
+ return resource.NewBuilder(mapper, typer, f.ClientMapperForCommand())
+}
diff --git a/pkg/kubectl/cmd/util/helpers.go b/pkg/kubectl/cmd/util/helpers.go
index f31dde47e4..33701071c0 100644
--- a/pkg/kubectl/cmd/util/helpers.go
+++ b/pkg/kubectl/cmd/util/helpers.go
@@ -76,6 +76,12 @@ func BehaviorOnFatal(f func(string)) {
fatalErrHandler = f
}
+// DefaultBehaviorOnFatal allows you to undo any previous override. Useful in
+// tests.
+func DefaultBehaviorOnFatal() {
+ fatalErrHandler = fatal
+}
+
// fatal prints the message and then exits. If V(2) or greater, glog.Fatal
// is invoked for extended information.
func fatal(msg string) {