2015-10-31 00:16:57 +00:00
/ *
2016-06-03 00:25:58 +00:00
Copyright 2015 The Kubernetes Authors .
2015-10-31 00:16:57 +00:00
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 (
2016-10-18 23:00:54 +00:00
"errors"
"fmt"
2015-10-31 00:16:57 +00:00
"io"
"io/ioutil"
"net/http"
"net/url"
"os"
"reflect"
2016-10-27 02:17:05 +00:00
"strconv"
2015-10-31 00:16:57 +00:00
"strings"
"testing"
"time"
"github.com/spf13/cobra"
2018-04-19 00:02:37 +00:00
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
2015-10-31 00:16:57 +00:00
2017-10-23 10:25:13 +00:00
corev1 "k8s.io/api/core/v1"
policyv1beta1 "k8s.io/api/policy/v1beta1"
2017-01-13 17:48:50 +00:00
apierrors "k8s.io/apimachinery/pkg/api/errors"
2017-01-11 14:09:48 +00:00
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
2017-06-30 12:58:57 +00:00
"k8s.io/apimachinery/pkg/util/strategicpatch"
2017-01-11 14:09:48 +00:00
"k8s.io/apimachinery/pkg/util/wait"
2017-01-24 15:00:24 +00:00
"k8s.io/client-go/rest/fake"
2015-10-31 00:16:57 +00:00
"k8s.io/kubernetes/pkg/api/testapi"
2016-04-18 15:44:19 +00:00
"k8s.io/kubernetes/pkg/apis/batch"
2017-11-08 22:34:54 +00:00
api "k8s.io/kubernetes/pkg/apis/core"
2015-10-31 00:16:57 +00:00
"k8s.io/kubernetes/pkg/apis/extensions"
2016-10-18 22:53:26 +00:00
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
2015-10-31 00:16:57 +00:00
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
2018-05-02 19:15:47 +00:00
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/printers"
2018-02-21 17:10:38 +00:00
"k8s.io/kubernetes/pkg/kubectl/scheme"
2015-10-31 00:16:57 +00:00
)
2016-10-20 16:43:48 +00:00
const (
EvictionMethod = "Eviction"
DeleteMethod = "Delete"
)
2017-10-23 10:25:13 +00:00
var node * corev1 . Node
var cordoned_node * corev1 . Node
2015-10-31 00:16:57 +00:00
2017-06-13 23:46:34 +00:00
func boolptr ( b bool ) * bool { return & b }
2015-10-31 00:16:57 +00:00
func TestMain ( m * testing . M ) {
// Create a node.
2017-10-23 10:25:13 +00:00
node = & corev1 . Node {
2017-01-17 03:38:19 +00:00
ObjectMeta : metav1 . ObjectMeta {
2015-10-31 00:16:57 +00:00
Name : "node" ,
2016-12-03 18:57:26 +00:00
CreationTimestamp : metav1 . Time { Time : time . Now ( ) } ,
2015-10-31 00:16:57 +00:00
} ,
2017-10-23 10:25:13 +00:00
Status : corev1 . NodeStatus { } ,
2015-10-31 00:16:57 +00:00
}
// A copy of the same node, but cordoned.
2017-08-15 12:13:20 +00:00
cordoned_node = node . DeepCopy ( )
2015-10-31 00:16:57 +00:00
cordoned_node . Spec . Unschedulable = true
os . Exit ( m . Run ( ) )
}
func TestCordon ( t * testing . T ) {
tests := [ ] struct {
description string
2017-10-23 10:25:13 +00:00
node * corev1 . Node
expected * corev1 . Node
2018-04-19 00:02:37 +00:00
cmd func ( cmdutil . Factory , genericclioptions . IOStreams ) * cobra . Command
2015-10-31 00:16:57 +00:00
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 {
2018-03-08 22:23:55 +00:00
t . Run ( test . description , func ( t * testing . T ) {
tf := cmdtesting . NewTestFactory ( )
defer tf . Cleanup ( )
2018-08-02 17:29:16 +00:00
codec := scheme . Codecs . LegacyCodec ( scheme . Scheme . PrioritizedVersionsAllGroups ( ) ... )
ns := scheme . Codecs
2018-03-08 22:23:55 +00:00
new_node := & corev1 . Node { }
updated := false
tf . Client = & fake . RESTClient {
2018-05-01 14:54:37 +00:00
GroupVersion : schema . GroupVersion { Group : "" , Version : "v1" } ,
2018-03-08 22:23:55 +00:00
NegotiatedSerializer : ns ,
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 , Header : defaultHeader ( ) , Body : objBody ( codec , test . node ) } , nil
case m . isFor ( "GET" , "/nodes/bar" ) :
return & http . Response { StatusCode : 404 , Header : defaultHeader ( ) , Body : stringBody ( "nope" ) } , nil
case m . isFor ( "PATCH" , "/nodes/node" ) :
data , err := ioutil . ReadAll ( req . Body )
if err != nil {
t . Fatalf ( "%s: unexpected error: %v" , test . description , err )
}
defer req . Body . Close ( )
oldJSON , err := runtime . Encode ( codec , node )
if err != nil {
t . Fatalf ( "%s: unexpected error: %v" , test . description , err )
}
appliedPatch , err := strategicpatch . StrategicMergePatch ( oldJSON , data , & corev1 . Node { } )
if err != nil {
t . Fatalf ( "%s: unexpected error: %v" , test . description , err )
}
if err := runtime . DecodeInto ( codec , appliedPatch , 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 . Unschedulable , new_node . Spec . Unschedulable )
}
updated = true
return & http . Response { StatusCode : 200 , Header : defaultHeader ( ) , Body : objBody ( codec , new_node ) } , nil
default :
t . Fatalf ( "%s: unexpected request: %v %#v\n%#v" , test . description , req . Method , req . URL , req )
return nil , nil
2015-10-31 00:16:57 +00:00
}
2018-03-08 22:23:55 +00:00
} ) ,
}
tf . ClientConfigVal = defaultClientConfig ( )
2018-04-24 19:38:38 +00:00
ioStreams , _ , _ , _ := genericclioptions . NewTestIOStreams ( )
cmd := test . cmd ( tf , ioStreams )
2018-03-08 22:23:55 +00:00
saw_fatal := false
func ( ) {
defer func ( ) {
// Recover from the panic below.
_ = recover ( )
// Restore cmdutil behavior
cmdutil . DefaultBehaviorOnFatal ( )
} ( )
cmdutil . BehaviorOnFatal ( func ( e string , code int ) {
saw_fatal = true
panic ( e )
} )
cmd . SetArgs ( [ ] string { test . arg } )
cmd . Execute ( )
2015-10-31 00:16:57 +00:00
} ( )
2018-03-08 22:23:55 +00:00
if test . expectFatal {
if ! saw_fatal {
t . Fatalf ( "%s: unexpected non-error" , test . description )
}
if updated {
t . Fatalf ( "%s: unexpected update" , test . description )
}
2015-10-31 00:16:57 +00:00
}
2018-03-08 22:23:55 +00:00
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 )
}
} )
2015-10-31 00:16:57 +00:00
}
}
func TestDrain ( t * testing . T ) {
labels := make ( map [ string ] string )
labels [ "my_key" ] = "my_value"
rc := api . ReplicationController {
2017-01-17 03:38:19 +00:00
ObjectMeta : metav1 . ObjectMeta {
2015-10-31 00:16:57 +00:00
Name : "rc" ,
Namespace : "default" ,
2016-12-03 18:57:26 +00:00
CreationTimestamp : metav1 . Time { Time : time . Now ( ) } ,
2015-10-31 00:16:57 +00:00
Labels : labels ,
SelfLink : testapi . Default . SelfLink ( "replicationcontrollers" , "rc" ) ,
} ,
Spec : api . ReplicationControllerSpec {
Selector : labels ,
} ,
}
2017-10-23 10:25:13 +00:00
rc_pod := corev1 . Pod {
2017-01-17 03:38:19 +00:00
ObjectMeta : metav1 . ObjectMeta {
2015-10-31 00:16:57 +00:00
Name : "bar" ,
Namespace : "default" ,
2016-12-03 18:57:26 +00:00
CreationTimestamp : metav1 . Time { Time : time . Now ( ) } ,
2015-10-31 00:16:57 +00:00
Labels : labels ,
2017-06-13 23:46:34 +00:00
SelfLink : testapi . Default . SelfLink ( "pods" , "bar" ) ,
OwnerReferences : [ ] metav1 . OwnerReference {
{
APIVersion : "v1" ,
Kind : "ReplicationController" ,
Name : "rc" ,
UID : "123" ,
BlockOwnerDeletion : boolptr ( true ) ,
Controller : boolptr ( true ) ,
} ,
} ,
2015-10-31 00:16:57 +00:00
} ,
2017-10-23 10:25:13 +00:00
Spec : corev1 . PodSpec {
2015-10-31 00:16:57 +00:00
NodeName : "node" ,
} ,
}
ds := extensions . DaemonSet {
2017-01-17 03:38:19 +00:00
ObjectMeta : metav1 . ObjectMeta {
2015-10-31 00:16:57 +00:00
Name : "ds" ,
Namespace : "default" ,
2016-12-03 18:57:26 +00:00
CreationTimestamp : metav1 . Time { Time : time . Now ( ) } ,
2017-06-13 23:46:34 +00:00
SelfLink : testapi . Default . SelfLink ( "daemonsets" , "ds" ) ,
2015-10-31 00:16:57 +00:00
} ,
Spec : extensions . DaemonSetSpec {
2016-12-03 18:57:26 +00:00
Selector : & metav1 . LabelSelector { MatchLabels : labels } ,
2015-10-31 00:16:57 +00:00
} ,
}
2017-10-23 10:25:13 +00:00
ds_pod := corev1 . Pod {
2017-01-17 03:38:19 +00:00
ObjectMeta : metav1 . ObjectMeta {
2015-10-31 00:16:57 +00:00
Name : "bar" ,
Namespace : "default" ,
2016-12-03 18:57:26 +00:00
CreationTimestamp : metav1 . Time { Time : time . Now ( ) } ,
2015-10-31 00:16:57 +00:00
Labels : labels ,
2017-06-13 23:46:34 +00:00
SelfLink : testapi . Default . SelfLink ( "pods" , "bar" ) ,
OwnerReferences : [ ] metav1 . OwnerReference {
{
APIVersion : "extensions/v1beta1" ,
Kind : "DaemonSet" ,
Name : "ds" ,
BlockOwnerDeletion : boolptr ( true ) ,
Controller : boolptr ( true ) ,
} ,
} ,
2015-10-31 00:16:57 +00:00
} ,
2017-10-23 10:25:13 +00:00
Spec : corev1 . PodSpec {
2015-10-31 00:16:57 +00:00
NodeName : "node" ,
} ,
}
2017-12-01 16:26:11 +00:00
ds_pod_with_emptyDir := corev1 . Pod {
ObjectMeta : metav1 . ObjectMeta {
Name : "bar" ,
Namespace : "default" ,
CreationTimestamp : metav1 . Time { Time : time . Now ( ) } ,
Labels : labels ,
SelfLink : testapi . Default . SelfLink ( "pods" , "bar" ) ,
OwnerReferences : [ ] metav1 . OwnerReference {
{
APIVersion : "extensions/v1beta1" ,
Kind : "DaemonSet" ,
Name : "ds" ,
BlockOwnerDeletion : boolptr ( true ) ,
Controller : boolptr ( true ) ,
} ,
} ,
} ,
Spec : corev1 . PodSpec {
NodeName : "node" ,
Volumes : [ ] corev1 . Volume {
{
Name : "scratch" ,
VolumeSource : corev1 . VolumeSource { EmptyDir : & corev1 . EmptyDirVolumeSource { Medium : "" } } ,
} ,
} ,
} ,
}
2017-10-23 10:25:13 +00:00
orphaned_ds_pod := corev1 . Pod {
2017-02-22 01:07:42 +00:00
ObjectMeta : metav1 . ObjectMeta {
Name : "bar" ,
Namespace : "default" ,
CreationTimestamp : metav1 . Time { Time : time . Now ( ) } ,
Labels : labels ,
2017-06-13 23:46:34 +00:00
SelfLink : testapi . Default . SelfLink ( "pods" , "bar" ) ,
2017-02-22 01:07:42 +00:00
} ,
2017-10-23 10:25:13 +00:00
Spec : corev1 . PodSpec {
2017-02-22 01:07:42 +00:00
NodeName : "node" ,
} ,
}
2016-04-18 15:44:19 +00:00
job := batch . Job {
2017-01-17 03:38:19 +00:00
ObjectMeta : metav1 . ObjectMeta {
2016-01-16 01:14:22 +00:00
Name : "job" ,
Namespace : "default" ,
2016-12-03 18:57:26 +00:00
CreationTimestamp : metav1 . Time { Time : time . Now ( ) } ,
2017-06-13 23:46:34 +00:00
SelfLink : testapi . Default . SelfLink ( "jobs" , "job" ) ,
2016-01-16 01:14:22 +00:00
} ,
2016-04-18 15:44:19 +00:00
Spec : batch . JobSpec {
2016-12-03 18:57:26 +00:00
Selector : & metav1 . LabelSelector { MatchLabels : labels } ,
2016-01-16 01:14:22 +00:00
} ,
}
2017-10-23 10:25:13 +00:00
job_pod := corev1 . Pod {
2017-01-17 03:38:19 +00:00
ObjectMeta : metav1 . ObjectMeta {
2016-01-16 01:14:22 +00:00
Name : "bar" ,
Namespace : "default" ,
2016-12-03 18:57:26 +00:00
CreationTimestamp : metav1 . Time { Time : time . Now ( ) } ,
2016-01-16 01:14:22 +00:00
Labels : labels ,
2017-06-13 23:46:34 +00:00
SelfLink : testapi . Default . SelfLink ( "pods" , "bar" ) ,
OwnerReferences : [ ] metav1 . OwnerReference {
{
APIVersion : "v1" ,
Kind : "Job" ,
Name : "job" ,
BlockOwnerDeletion : boolptr ( true ) ,
Controller : boolptr ( true ) ,
} ,
} ,
2016-01-16 01:14:22 +00:00
} ,
}
2016-03-31 18:50:09 +00:00
rs := extensions . ReplicaSet {
2017-01-17 03:38:19 +00:00
ObjectMeta : metav1 . ObjectMeta {
2016-03-31 18:50:09 +00:00
Name : "rs" ,
Namespace : "default" ,
2016-12-03 18:57:26 +00:00
CreationTimestamp : metav1 . Time { Time : time . Now ( ) } ,
2016-03-31 18:50:09 +00:00
Labels : labels ,
SelfLink : testapi . Default . SelfLink ( "replicasets" , "rs" ) ,
} ,
Spec : extensions . ReplicaSetSpec {
2016-12-03 18:57:26 +00:00
Selector : & metav1 . LabelSelector { MatchLabels : labels } ,
2016-03-31 18:50:09 +00:00
} ,
}
2017-10-23 10:25:13 +00:00
rs_pod := corev1 . Pod {
2017-01-17 03:38:19 +00:00
ObjectMeta : metav1 . ObjectMeta {
2016-03-31 18:50:09 +00:00
Name : "bar" ,
Namespace : "default" ,
2016-12-03 18:57:26 +00:00
CreationTimestamp : metav1 . Time { Time : time . Now ( ) } ,
2016-03-31 18:50:09 +00:00
Labels : labels ,
2017-06-13 23:46:34 +00:00
SelfLink : testapi . Default . SelfLink ( "pods" , "bar" ) ,
OwnerReferences : [ ] metav1 . OwnerReference {
{
APIVersion : "v1" ,
Kind : "ReplicaSet" ,
Name : "rs" ,
BlockOwnerDeletion : boolptr ( true ) ,
Controller : boolptr ( true ) ,
} ,
} ,
2016-03-31 18:50:09 +00:00
} ,
2017-10-23 10:25:13 +00:00
Spec : corev1 . PodSpec {
2016-03-31 18:50:09 +00:00
NodeName : "node" ,
} ,
}
2017-10-23 10:25:13 +00:00
naked_pod := corev1 . Pod {
2017-01-17 03:38:19 +00:00
ObjectMeta : metav1 . ObjectMeta {
2015-10-31 00:16:57 +00:00
Name : "bar" ,
Namespace : "default" ,
2016-12-03 18:57:26 +00:00
CreationTimestamp : metav1 . Time { Time : time . Now ( ) } ,
2015-10-31 00:16:57 +00:00
Labels : labels ,
} ,
2017-10-23 10:25:13 +00:00
Spec : corev1 . PodSpec {
2015-10-31 00:16:57 +00:00
NodeName : "node" ,
} ,
}
2017-10-23 10:25:13 +00:00
emptydir_pod := corev1 . Pod {
2017-01-17 03:38:19 +00:00
ObjectMeta : metav1 . ObjectMeta {
2016-06-01 21:50:13 +00:00
Name : "bar" ,
Namespace : "default" ,
2016-12-03 18:57:26 +00:00
CreationTimestamp : metav1 . Time { Time : time . Now ( ) } ,
2016-06-01 21:50:13 +00:00
Labels : labels ,
} ,
2017-10-23 10:25:13 +00:00
Spec : corev1 . PodSpec {
2016-06-01 21:50:13 +00:00
NodeName : "node" ,
2017-10-23 10:25:13 +00:00
Volumes : [ ] corev1 . Volume {
2016-06-01 21:50:13 +00:00
{
Name : "scratch" ,
2017-10-23 10:25:13 +00:00
VolumeSource : corev1 . VolumeSource { EmptyDir : & corev1 . EmptyDirVolumeSource { Medium : "" } } ,
2016-06-01 21:50:13 +00:00
} ,
} ,
} ,
}
2015-10-31 00:16:57 +00:00
tests := [ ] struct {
2017-12-01 16:26:11 +00:00
description string
node * corev1 . Node
expected * corev1 . Node
pods [ ] corev1 . Pod
rcs [ ] api . ReplicationController
replicaSets [ ] extensions . ReplicaSet
args [ ] string
expectWarning string
expectFatal bool
expectDelete bool
2015-10-31 00:16:57 +00:00
} {
{
description : "RC-managed pod" ,
node : node ,
expected : cordoned_node ,
2017-10-23 10:25:13 +00:00
pods : [ ] corev1 . Pod { rc_pod } ,
2015-10-31 00:16:57 +00:00
rcs : [ ] api . ReplicationController { rc } ,
args : [ ] string { "node" } ,
expectFatal : false ,
expectDelete : true ,
} ,
{
description : "DS-managed pod" ,
node : node ,
expected : cordoned_node ,
2017-10-23 10:25:13 +00:00
pods : [ ] corev1 . Pod { ds_pod } ,
2015-10-31 00:16:57 +00:00
rcs : [ ] api . ReplicationController { rc } ,
args : [ ] string { "node" } ,
2016-01-27 18:27:14 +00:00
expectFatal : true ,
expectDelete : false ,
} ,
2017-02-22 01:07:42 +00:00
{
description : "orphaned DS-managed pod" ,
node : node ,
expected : cordoned_node ,
2017-10-23 10:25:13 +00:00
pods : [ ] corev1 . Pod { orphaned_ds_pod } ,
2017-02-22 01:07:42 +00:00
rcs : [ ] api . ReplicationController { } ,
args : [ ] string { "node" } ,
expectFatal : true ,
expectDelete : false ,
} ,
{
description : "orphaned DS-managed pod with --force" ,
node : node ,
expected : cordoned_node ,
2017-10-23 10:25:13 +00:00
pods : [ ] corev1 . Pod { orphaned_ds_pod } ,
2017-02-22 01:07:42 +00:00
rcs : [ ] api . ReplicationController { } ,
args : [ ] string { "node" , "--force" } ,
expectFatal : false ,
expectDelete : true ,
} ,
2016-01-27 18:27:14 +00:00
{
description : "DS-managed pod with --ignore-daemonsets" ,
node : node ,
expected : cordoned_node ,
2017-10-23 10:25:13 +00:00
pods : [ ] corev1 . Pod { ds_pod } ,
2016-01-27 18:27:14 +00:00
rcs : [ ] api . ReplicationController { rc } ,
args : [ ] string { "node" , "--ignore-daemonsets" } ,
2015-10-31 00:16:57 +00:00
expectFatal : false ,
2016-01-27 18:27:14 +00:00
expectDelete : false ,
2015-10-31 00:16:57 +00:00
} ,
2017-12-01 16:26:11 +00:00
{
description : "DS-managed pod with emptyDir with --ignore-daemonsets" ,
node : node ,
expected : cordoned_node ,
pods : [ ] corev1 . Pod { ds_pod_with_emptyDir } ,
rcs : [ ] api . ReplicationController { rc } ,
args : [ ] string { "node" , "--ignore-daemonsets" } ,
expectWarning : "WARNING: Ignoring DaemonSet-managed pods: bar\n" ,
expectFatal : false ,
expectDelete : false ,
} ,
2016-01-16 01:14:22 +00:00
{
description : "Job-managed pod" ,
node : node ,
expected : cordoned_node ,
2017-10-23 10:25:13 +00:00
pods : [ ] corev1 . Pod { job_pod } ,
2016-01-16 01:14:22 +00:00
rcs : [ ] api . ReplicationController { rc } ,
args : [ ] string { "node" } ,
expectFatal : false ,
expectDelete : true ,
} ,
2016-03-31 18:50:09 +00:00
{
description : "RS-managed pod" ,
node : node ,
expected : cordoned_node ,
2017-10-23 10:25:13 +00:00
pods : [ ] corev1 . Pod { rs_pod } ,
2016-03-31 18:50:09 +00:00
replicaSets : [ ] extensions . ReplicaSet { rs } ,
args : [ ] string { "node" } ,
expectFatal : false ,
expectDelete : true ,
} ,
2015-10-31 00:16:57 +00:00
{
description : "naked pod" ,
node : node ,
expected : cordoned_node ,
2017-10-23 10:25:13 +00:00
pods : [ ] corev1 . Pod { naked_pod } ,
2015-10-31 00:16:57 +00:00
rcs : [ ] api . ReplicationController { } ,
args : [ ] string { "node" } ,
expectFatal : true ,
expectDelete : false ,
} ,
{
description : "naked pod with --force" ,
node : node ,
expected : cordoned_node ,
2017-10-23 10:25:13 +00:00
pods : [ ] corev1 . Pod { naked_pod } ,
2015-10-31 00:16:57 +00:00
rcs : [ ] api . ReplicationController { } ,
args : [ ] string { "node" , "--force" } ,
expectFatal : false ,
expectDelete : true ,
} ,
2016-06-01 21:50:13 +00:00
{
description : "pod with EmptyDir" ,
node : node ,
expected : cordoned_node ,
2017-10-23 10:25:13 +00:00
pods : [ ] corev1 . Pod { emptydir_pod } ,
2016-06-01 21:50:13 +00:00
args : [ ] string { "node" , "--force" } ,
expectFatal : true ,
expectDelete : false ,
} ,
{
description : "pod with EmptyDir and --delete-local-data" ,
node : node ,
expected : cordoned_node ,
2017-10-23 10:25:13 +00:00
pods : [ ] corev1 . Pod { emptydir_pod } ,
2016-06-01 21:50:13 +00:00
args : [ ] string { "node" , "--force" , "--delete-local-data=true" } ,
expectFatal : false ,
expectDelete : true ,
} ,
2015-10-31 00:16:57 +00:00
{
description : "empty node" ,
node : node ,
expected : cordoned_node ,
2017-10-23 10:25:13 +00:00
pods : [ ] corev1 . Pod { } ,
2015-10-31 00:16:57 +00:00
rcs : [ ] api . ReplicationController { rc } ,
args : [ ] string { "node" } ,
expectFatal : false ,
expectDelete : false ,
} ,
}
2016-10-20 16:43:48 +00:00
testEviction := false
for i := 0 ; i < 2 ; i ++ {
testEviction = ! testEviction
var currMethod string
if testEviction {
currMethod = EvictionMethod
} else {
currMethod = DeleteMethod
2015-10-31 00:16:57 +00:00
}
2016-10-20 16:43:48 +00:00
for _ , test := range tests {
2018-03-08 22:23:55 +00:00
t . Run ( test . description , func ( t * testing . T ) {
new_node := & corev1 . Node { }
deleted := false
evicted := false
tf := cmdtesting . NewTestFactory ( )
defer tf . Cleanup ( )
2018-08-02 17:29:16 +00:00
codec := scheme . Codecs . LegacyCodec ( scheme . Scheme . PrioritizedVersionsAllGroups ( ) ... )
ns := scheme . Codecs
2018-03-08 22:23:55 +00:00
tf . Client = & fake . RESTClient {
2018-05-01 14:54:37 +00:00
GroupVersion : schema . GroupVersion { Group : "" , Version : "v1" } ,
2018-03-08 22:23:55 +00:00
NegotiatedSerializer : ns ,
Client : fake . CreateHTTPClient ( func ( req * http . Request ) ( * http . Response , error ) {
m := & MyReq { req }
switch {
case req . Method == "GET" && req . URL . Path == "/api" :
apiVersions := metav1 . APIVersions {
Versions : [ ] string { "v1" } ,
}
return genResponseWithJsonEncodedBody ( apiVersions )
case req . Method == "GET" && req . URL . Path == "/apis" :
groupList := metav1 . APIGroupList {
Groups : [ ] metav1 . APIGroup {
{
Name : "policy" ,
PreferredVersion : metav1 . GroupVersionForDiscovery {
GroupVersion : "policy/v1beta1" ,
} ,
2016-10-20 16:43:48 +00:00
} ,
} ,
}
2018-03-08 22:23:55 +00:00
return genResponseWithJsonEncodedBody ( groupList )
case req . Method == "GET" && req . URL . Path == "/api/v1" :
resourceList := metav1 . APIResourceList {
GroupVersion : "v1" ,
}
if testEviction {
resourceList . APIResources = [ ] metav1 . APIResource {
{
Name : EvictionSubresource ,
Kind : EvictionKind ,
} ,
}
}
return genResponseWithJsonEncodedBody ( resourceList )
case m . isFor ( "GET" , "/nodes/node" ) :
return & http . Response { StatusCode : 200 , Header : defaultHeader ( ) , Body : objBody ( codec , test . node ) } , nil
case m . isFor ( "GET" , "/namespaces/default/replicationcontrollers/rc" ) :
return & http . Response { StatusCode : 200 , Header : defaultHeader ( ) , Body : objBody ( codec , & test . rcs [ 0 ] ) } , nil
case m . isFor ( "GET" , "/namespaces/default/daemonsets/ds" ) :
return & http . Response { StatusCode : 200 , Header : defaultHeader ( ) , Body : objBody ( testapi . Extensions . Codec ( ) , & ds ) } , nil
case m . isFor ( "GET" , "/namespaces/default/daemonsets/missing-ds" ) :
return & http . Response { StatusCode : 404 , Header : defaultHeader ( ) , Body : objBody ( testapi . Extensions . Codec ( ) , & extensions . DaemonSet { } ) } , nil
case m . isFor ( "GET" , "/namespaces/default/jobs/job" ) :
return & http . Response { StatusCode : 200 , Header : defaultHeader ( ) , Body : objBody ( testapi . Batch . Codec ( ) , & job ) } , nil
case m . isFor ( "GET" , "/namespaces/default/replicasets/rs" ) :
return & http . Response { StatusCode : 200 , Header : defaultHeader ( ) , Body : objBody ( testapi . Extensions . Codec ( ) , & test . replicaSets [ 0 ] ) } , nil
case m . isFor ( "GET" , "/namespaces/default/pods/bar" ) :
return & http . Response { StatusCode : 404 , Header : defaultHeader ( ) , Body : objBody ( codec , & corev1 . Pod { } ) } , 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 , Header : defaultHeader ( ) , Body : objBody ( codec , & corev1 . PodList { Items : test . pods } ) } , nil
case m . isFor ( "GET" , "/replicationcontrollers" ) :
return & http . Response { StatusCode : 200 , Header : defaultHeader ( ) , Body : objBody ( codec , & api . ReplicationControllerList { Items : test . rcs } ) } , nil
case m . isFor ( "PATCH" , "/nodes/node" ) :
data , err := ioutil . ReadAll ( req . Body )
if err != nil {
t . Fatalf ( "%s: unexpected error: %v" , test . description , err )
}
defer req . Body . Close ( )
oldJSON , err := runtime . Encode ( codec , node )
if err != nil {
t . Fatalf ( "%s: unexpected error: %v" , test . description , err )
}
appliedPatch , err := strategicpatch . StrategicMergePatch ( oldJSON , data , & corev1 . Node { } )
if err != nil {
t . Fatalf ( "%s: unexpected error: %v" , test . description , err )
}
if err := runtime . DecodeInto ( codec , appliedPatch , 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 , Header : defaultHeader ( ) , Body : objBody ( codec , new_node ) } , nil
case m . isFor ( "DELETE" , "/namespaces/default/pods/bar" ) :
deleted = true
return & http . Response { StatusCode : 204 , Header : defaultHeader ( ) , Body : objBody ( codec , & test . pods [ 0 ] ) } , nil
case m . isFor ( "POST" , "/namespaces/default/pods/bar/eviction" ) :
evicted = true
return & http . Response { StatusCode : 201 , Header : defaultHeader ( ) , Body : policyObjBody ( & policyv1beta1 . Eviction { } ) } , nil
default :
t . Fatalf ( "%s: unexpected request: %v %#v\n%#v" , test . description , req . Method , req . URL , req )
return nil , nil
2016-10-20 16:43:48 +00:00
}
2018-03-08 22:23:55 +00:00
} ) ,
2016-10-20 16:43:48 +00:00
}
2018-03-08 22:23:55 +00:00
tf . ClientConfigVal = defaultClientConfig ( )
2018-04-24 19:38:38 +00:00
ioStreams , _ , _ , errBuf := genericclioptions . NewTestIOStreams ( )
cmd := NewCmdDrain ( tf , ioStreams )
2018-03-08 22:23:55 +00:00
saw_fatal := false
fatal_msg := ""
func ( ) {
defer func ( ) {
// Recover from the panic below.
_ = recover ( )
// Restore cmdutil behavior
cmdutil . DefaultBehaviorOnFatal ( )
} ( )
cmdutil . BehaviorOnFatal ( func ( e string , code int ) { saw_fatal = true ; fatal_msg = e ; panic ( e ) } )
cmd . SetArgs ( test . args )
cmd . Execute ( )
} ( )
if test . expectFatal {
if ! saw_fatal {
t . Fatalf ( "%s: unexpected non-error when using %s" , test . description , currMethod )
}
} else {
if saw_fatal {
t . Fatalf ( "%s: unexpected error when using %s: %s" , test . description , currMethod , fatal_msg )
2017-12-01 16:26:11 +00:00
2018-03-08 22:23:55 +00:00
}
2017-12-01 16:26:11 +00:00
}
2015-10-31 00:16:57 +00:00
2018-03-08 22:23:55 +00:00
if test . expectDelete {
// Test Delete
if ! testEviction && ! deleted {
t . Fatalf ( "%s: pod never deleted" , test . description )
}
// Test Eviction
if testEviction && ! evicted {
t . Fatalf ( "%s: pod never evicted" , test . description )
}
2016-10-20 16:43:48 +00:00
}
2018-03-08 22:23:55 +00:00
if ! test . expectDelete {
if deleted {
t . Fatalf ( "%s: unexpected delete when using %s" , test . description , currMethod )
}
2016-10-20 16:43:48 +00:00
}
2017-12-01 16:26:11 +00:00
2018-03-08 22:23:55 +00:00
if len ( test . expectWarning ) > 0 {
if len ( errBuf . String ( ) ) == 0 {
t . Fatalf ( "%s: expected warning, but found no stderr output" , test . description )
}
2017-12-01 16:26:11 +00:00
2018-03-08 22:23:55 +00:00
if errBuf . String ( ) != test . expectWarning {
t . Fatalf ( "%s: actual warning message did not match expected warning message.\n Expecting: %s\n Got: %s" , test . description , test . expectWarning , errBuf . String ( ) )
}
2017-12-01 16:26:11 +00:00
}
2018-03-08 22:23:55 +00:00
} )
2015-10-31 00:16:57 +00:00
}
}
}
2016-10-18 23:00:54 +00:00
func TestDeletePods ( t * testing . T ) {
2016-10-19 21:56:15 +00:00
ifHasBeenCalled := map [ string ] bool { }
2016-10-18 23:00:54 +00:00
tests := [ ] struct {
description string
interval time . Duration
timeout time . Duration
expectPendingPods bool
expectError bool
2016-10-19 21:56:15 +00:00
expectedError * error
2017-10-23 10:25:13 +00:00
getPodFn func ( namespace , name string ) ( * corev1 . Pod , error )
2016-10-18 23:00:54 +00:00
} {
{
description : "Wait for deleting to complete" ,
interval : 100 * time . Millisecond ,
timeout : 10 * time . Second ,
expectPendingPods : false ,
expectError : false ,
2016-10-19 21:56:15 +00:00
expectedError : nil ,
2017-10-23 10:25:13 +00:00
getPodFn : func ( namespace , name string ) ( * corev1 . Pod , error ) {
2016-10-18 23:00:54 +00:00
oldPodMap , _ := createPods ( false )
newPodMap , _ := createPods ( true )
2016-10-19 21:56:15 +00:00
if oldPod , found := oldPodMap [ name ] ; found {
if _ , ok := ifHasBeenCalled [ name ] ; ! ok {
ifHasBeenCalled [ name ] = true
2016-10-18 23:00:54 +00:00
return & oldPod , nil
}
2016-12-06 08:27:52 +00:00
if oldPod . ObjectMeta . Generation < 4 {
newPod := newPodMap [ name ]
return & newPod , nil
}
return nil , apierrors . NewNotFound ( schema . GroupResource { Resource : "pods" } , name )
2016-10-18 23:00:54 +00:00
}
2016-11-21 02:55:31 +00:00
return nil , apierrors . NewNotFound ( schema . GroupResource { Resource : "pods" } , name )
2016-10-18 23:00:54 +00:00
} ,
} ,
{
description : "Deleting could timeout" ,
interval : 200 * time . Millisecond ,
timeout : 3 * time . Second ,
expectPendingPods : true ,
expectError : true ,
2016-10-19 21:56:15 +00:00
expectedError : & wait . ErrWaitTimeout ,
2017-10-23 10:25:13 +00:00
getPodFn : func ( namespace , name string ) ( * corev1 . Pod , error ) {
2016-10-18 23:00:54 +00:00
oldPodMap , _ := createPods ( false )
if oldPod , found := oldPodMap [ name ] ; found {
return & oldPod , nil
}
2016-12-06 08:27:52 +00:00
return nil , fmt . Errorf ( "%q: not found" , name )
2016-10-19 21:56:15 +00:00
} ,
} ,
{
description : "Client error could be passed out" ,
interval : 200 * time . Millisecond ,
timeout : 5 * time . Second ,
expectPendingPods : true ,
expectError : true ,
expectedError : nil ,
2017-10-23 10:25:13 +00:00
getPodFn : func ( namespace , name string ) ( * corev1 . Pod , error ) {
2016-10-19 21:56:15 +00:00
return nil , errors . New ( "This is a random error for testing" )
2016-10-18 23:00:54 +00:00
} ,
} ,
}
for _ , test := range tests {
2018-03-08 22:23:55 +00:00
t . Run ( test . description , func ( t * testing . T ) {
tf := cmdtesting . NewTestFactory ( )
defer tf . Cleanup ( )
2018-04-19 00:02:37 +00:00
o := DrainOptions {
2018-05-02 19:15:47 +00:00
PrintFlags : genericclioptions . NewPrintFlags ( "drained" ) . WithTypeSetter ( scheme . Scheme ) ,
2018-04-19 00:02:37 +00:00
}
2018-03-08 22:23:55 +00:00
o . Out = os . Stdout
2018-04-19 00:02:37 +00:00
o . ToPrinter = func ( operation string ) ( printers . ResourcePrinterFunc , error ) {
return func ( obj runtime . Object , out io . Writer ) error {
return nil
} , nil
}
2018-03-08 22:23:55 +00:00
_ , pods := createPods ( false )
pendingPods , err := o . waitForDelete ( pods , test . interval , test . timeout , false , test . getPodFn )
if test . expectError {
if err == nil {
t . Fatalf ( "%s: unexpected non-error" , test . description )
} else if test . expectedError != nil {
if * test . expectedError != err {
t . Fatalf ( "%s: the error does not match expected error" , test . description )
}
2016-10-19 21:56:15 +00:00
}
}
2018-03-08 22:23:55 +00:00
if ! test . expectError && err != nil {
t . Fatalf ( "%s: unexpected error" , test . description )
}
if test . expectPendingPods && len ( pendingPods ) == 0 {
t . Fatalf ( "%s: unexpected empty pods" , test . description )
}
if ! test . expectPendingPods && len ( pendingPods ) > 0 {
t . Fatalf ( "%s: unexpected pending pods" , test . description )
}
} )
2016-10-18 23:00:54 +00:00
}
}
2017-10-23 10:25:13 +00:00
func createPods ( ifCreateNewPods bool ) ( map [ string ] corev1 . Pod , [ ] corev1 . Pod ) {
podMap := make ( map [ string ] corev1 . Pod )
podSlice := [ ] corev1 . Pod { }
2016-10-18 23:00:54 +00:00
for i := 0 ; i < 8 ; i ++ {
var uid types . UID
if ifCreateNewPods {
uid = types . UID ( i )
} else {
2016-10-27 02:17:05 +00:00
uid = types . UID ( strconv . Itoa ( i ) + strconv . Itoa ( i ) )
2016-10-18 23:00:54 +00:00
}
2017-10-23 10:25:13 +00:00
pod := corev1 . Pod {
2017-01-17 03:38:19 +00:00
ObjectMeta : metav1 . ObjectMeta {
2016-10-27 02:17:05 +00:00
Name : "pod" + strconv . Itoa ( i ) ,
2016-10-19 21:56:15 +00:00
Namespace : "default" ,
UID : uid ,
Generation : int64 ( i ) ,
2016-10-18 23:00:54 +00:00
} ,
}
podMap [ pod . Name ] = pod
podSlice = append ( podSlice , pod )
}
return podMap , podSlice
}
2015-10-31 00:16:57 +00:00
type MyReq struct {
Request * http . Request
}
func ( m * MyReq ) isFor ( method string , path string ) bool {
req := m . Request
2016-09-07 20:29:57 +00:00
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 } , "" ) ||
req . URL . Path == strings . Join ( [ ] string { "/apis/batch/v1" , path } , "" ) )
2015-10-31 00:16:57 +00:00
}