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 @@ + + + + +WARNING +WARNING +WARNING +WARNING +WARNING + +

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

+ +If you are using a released version of Kubernetes, you should +refer to the docs that go with that version. + +Documentation for other releases can be found at +[releases.k8s.io](http://releases.k8s.io). + +-- + + + + + +## kubectl 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 @@ + + + + +WARNING +WARNING +WARNING +WARNING +WARNING + +

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

+ +If you are using a released version of Kubernetes, you should +refer to the docs that go with that version. + +Documentation for other releases can be found at +[releases.k8s.io](http://releases.k8s.io). + +-- + + + + + +## kubectl 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 @@ + + + + +WARNING +WARNING +WARNING +WARNING +WARNING + +

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

+ +If you are using a released version of Kubernetes, you should +refer to the docs that go with that version. + +Documentation for other releases can be found at +[releases.k8s.io](http://releases.k8s.io). + +-- + + + + + +## kubectl 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) {