Initial node drain implementation for #3885.

It cordons (marks unschedulable) the given node, and then deletes every
pod on it, optionally using a grace period.  It will not delete pods
managed by neither a ReplicationController nor a DaemonSet without the
use of --force.

Also add cordon/uncordon, which just toggle node schedulability.
pull/6/head
Matt Liggett 2015-10-30 17:16:57 -07:00
parent 547bf75b54
commit c6e9ad066e
17 changed files with 1621 additions and 2 deletions

View File

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

View File

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

View File

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

View File

@ -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!

View File

@ -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!

View File

@ -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!

View File

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

View File

@ -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
<!-- BEGIN MUNGE: GENERATED_ANALYTICS -->
[![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/docs/user-guide/kubectl/kubectl.md?pixel)]()

View File

@ -0,0 +1,88 @@
<!-- BEGIN MUNGE: UNVERSIONED_WARNING -->
<!-- BEGIN STRIP_FOR_RELEASE -->
<img src="http://kubernetes.io/img/warning.png" alt="WARNING"
width="25" height="25">
<img src="http://kubernetes.io/img/warning.png" alt="WARNING"
width="25" height="25">
<img src="http://kubernetes.io/img/warning.png" alt="WARNING"
width="25" height="25">
<img src="http://kubernetes.io/img/warning.png" alt="WARNING"
width="25" height="25">
<img src="http://kubernetes.io/img/warning.png" alt="WARNING"
width="25" height="25">
<h2>PLEASE NOTE: This document applies to the HEAD of the source tree</h2>
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).
</strong>
--
<!-- END STRIP_FOR_RELEASE -->
<!-- END MUNGE: UNVERSIONED_WARNING -->
## 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
<!-- BEGIN MUNGE: GENERATED_ANALYTICS -->
[![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/docs/user-guide/kubectl/kubectl_cordon.md?pixel)]()
<!-- END MUNGE: GENERATED_ANALYTICS -->

View File

@ -0,0 +1,107 @@
<!-- BEGIN MUNGE: UNVERSIONED_WARNING -->
<!-- BEGIN STRIP_FOR_RELEASE -->
<img src="http://kubernetes.io/img/warning.png" alt="WARNING"
width="25" height="25">
<img src="http://kubernetes.io/img/warning.png" alt="WARNING"
width="25" height="25">
<img src="http://kubernetes.io/img/warning.png" alt="WARNING"
width="25" height="25">
<img src="http://kubernetes.io/img/warning.png" alt="WARNING"
width="25" height="25">
<img src="http://kubernetes.io/img/warning.png" alt="WARNING"
width="25" height="25">
<h2>PLEASE NOTE: This document applies to the HEAD of the source tree</h2>
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).
</strong>
--
<!-- END STRIP_FOR_RELEASE -->
<!-- END MUNGE: UNVERSIONED_WARNING -->
## 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
<!-- BEGIN MUNGE: GENERATED_ANALYTICS -->
[![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/docs/user-guide/kubectl/kubectl_drain.md?pixel)]()
<!-- END MUNGE: GENERATED_ANALYTICS -->

View File

@ -0,0 +1,88 @@
<!-- BEGIN MUNGE: UNVERSIONED_WARNING -->
<!-- BEGIN STRIP_FOR_RELEASE -->
<img src="http://kubernetes.io/img/warning.png" alt="WARNING"
width="25" height="25">
<img src="http://kubernetes.io/img/warning.png" alt="WARNING"
width="25" height="25">
<img src="http://kubernetes.io/img/warning.png" alt="WARNING"
width="25" height="25">
<img src="http://kubernetes.io/img/warning.png" alt="WARNING"
width="25" height="25">
<img src="http://kubernetes.io/img/warning.png" alt="WARNING"
width="25" height="25">
<h2>PLEASE NOTE: This document applies to the HEAD of the source tree</h2>
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).
</strong>
--
<!-- END STRIP_FOR_RELEASE -->
<!-- END MUNGE: UNVERSIONED_WARNING -->
## 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
<!-- BEGIN MUNGE: GENERATED_ANALYTICS -->
[![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/docs/user-guide/kubectl/kubectl_uncordon.md?pixel)]()
<!-- END MUNGE: GENERATED_ANALYTICS -->

View File

@ -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))

View File

@ -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) {

314
pkg/kubectl/cmd/drain.go Normal file
View File

@ -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"
}

View File

@ -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)
}

View File

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

View File

@ -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) {