...

Source file src/k8s.io/kubernetes/pkg/controller/controller_utils_test.go

Documentation: k8s.io/kubernetes/pkg/controller

     1  /*
     2  Copyright 2015 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package controller
    18  
    19  import (
    20  	"context"
    21  	"encoding/json"
    22  	"fmt"
    23  	"math"
    24  	"math/rand"
    25  	"net/http/httptest"
    26  	"sort"
    27  	"sync"
    28  	"testing"
    29  	"time"
    30  
    31  	apps "k8s.io/api/apps/v1"
    32  	v1 "k8s.io/api/core/v1"
    33  	apiequality "k8s.io/apimachinery/pkg/api/equality"
    34  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    35  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    36  	"k8s.io/apimachinery/pkg/runtime"
    37  	"k8s.io/apimachinery/pkg/runtime/schema"
    38  	"k8s.io/apimachinery/pkg/types"
    39  	"k8s.io/apimachinery/pkg/util/sets"
    40  	"k8s.io/apimachinery/pkg/util/uuid"
    41  	utilfeature "k8s.io/apiserver/pkg/util/feature"
    42  	clientset "k8s.io/client-go/kubernetes"
    43  	"k8s.io/client-go/kubernetes/fake"
    44  	clientscheme "k8s.io/client-go/kubernetes/scheme"
    45  	restclient "k8s.io/client-go/rest"
    46  	"k8s.io/client-go/tools/cache"
    47  	"k8s.io/client-go/tools/record"
    48  	utiltesting "k8s.io/client-go/util/testing"
    49  	featuregatetesting "k8s.io/component-base/featuregate/testing"
    50  	"k8s.io/kubernetes/pkg/apis/core"
    51  	_ "k8s.io/kubernetes/pkg/apis/core/install"
    52  	"k8s.io/kubernetes/pkg/controller/testutil"
    53  	"k8s.io/kubernetes/pkg/features"
    54  	"k8s.io/kubernetes/pkg/securitycontext"
    55  	"k8s.io/kubernetes/test/utils/ktesting"
    56  	testingclock "k8s.io/utils/clock/testing"
    57  	"k8s.io/utils/pointer"
    58  
    59  	"github.com/google/go-cmp/cmp"
    60  	"github.com/stretchr/testify/assert"
    61  )
    62  
    63  // NewFakeControllerExpectationsLookup creates a fake store for PodExpectations.
    64  func NewFakeControllerExpectationsLookup(ttl time.Duration) (*ControllerExpectations, *testingclock.FakeClock) {
    65  	fakeTime := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)
    66  	fakeClock := testingclock.NewFakeClock(fakeTime)
    67  	ttlPolicy := &cache.TTLPolicy{TTL: ttl, Clock: fakeClock}
    68  	ttlStore := cache.NewFakeExpirationStore(
    69  		ExpKeyFunc, nil, ttlPolicy, fakeClock)
    70  	return &ControllerExpectations{ttlStore}, fakeClock
    71  }
    72  
    73  func newReplicationController(replicas int) *v1.ReplicationController {
    74  	rc := &v1.ReplicationController{
    75  		TypeMeta: metav1.TypeMeta{APIVersion: "v1"},
    76  		ObjectMeta: metav1.ObjectMeta{
    77  			UID:             uuid.NewUUID(),
    78  			Name:            "foobar",
    79  			Namespace:       metav1.NamespaceDefault,
    80  			ResourceVersion: "18",
    81  		},
    82  		Spec: v1.ReplicationControllerSpec{
    83  			Replicas: pointer.Int32(int32(replicas)),
    84  			Selector: map[string]string{"foo": "bar"},
    85  			Template: &v1.PodTemplateSpec{
    86  				ObjectMeta: metav1.ObjectMeta{
    87  					Labels: map[string]string{
    88  						"name": "foo",
    89  						"type": "production",
    90  					},
    91  				},
    92  				Spec: v1.PodSpec{
    93  					Containers: []v1.Container{
    94  						{
    95  							Image:                  "foo/bar",
    96  							TerminationMessagePath: v1.TerminationMessagePathDefault,
    97  							ImagePullPolicy:        v1.PullIfNotPresent,
    98  							SecurityContext:        securitycontext.ValidSecurityContextWithContainerDefaults(),
    99  						},
   100  					},
   101  					RestartPolicy: v1.RestartPolicyAlways,
   102  					DNSPolicy:     v1.DNSDefault,
   103  					NodeSelector: map[string]string{
   104  						"baz": "blah",
   105  					},
   106  				},
   107  			},
   108  		},
   109  	}
   110  	return rc
   111  }
   112  
   113  // create count pods with the given phase for the given rc (same selectors and namespace), and add them to the store.
   114  func newPodList(store cache.Store, count int, status v1.PodPhase, rc *v1.ReplicationController) *v1.PodList {
   115  	pods := []v1.Pod{}
   116  	for i := 0; i < count; i++ {
   117  		newPod := v1.Pod{
   118  			ObjectMeta: metav1.ObjectMeta{
   119  				Name:      fmt.Sprintf("pod%d", i),
   120  				Labels:    rc.Spec.Selector,
   121  				Namespace: rc.Namespace,
   122  			},
   123  			Status: v1.PodStatus{Phase: status},
   124  		}
   125  		if store != nil {
   126  			store.Add(&newPod)
   127  		}
   128  		pods = append(pods, newPod)
   129  	}
   130  	return &v1.PodList{
   131  		Items: pods,
   132  	}
   133  }
   134  
   135  func newReplicaSet(name string, replicas int, rsUuid types.UID) *apps.ReplicaSet {
   136  	return &apps.ReplicaSet{
   137  		TypeMeta: metav1.TypeMeta{APIVersion: "v1"},
   138  		ObjectMeta: metav1.ObjectMeta{
   139  			UID:             rsUuid,
   140  			Name:            name,
   141  			Namespace:       metav1.NamespaceDefault,
   142  			ResourceVersion: "18",
   143  		},
   144  		Spec: apps.ReplicaSetSpec{
   145  			Replicas: pointer.Int32(int32(replicas)),
   146  			Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}},
   147  			Template: v1.PodTemplateSpec{
   148  				ObjectMeta: metav1.ObjectMeta{
   149  					Labels: map[string]string{
   150  						"name": "foo",
   151  						"type": "production",
   152  					},
   153  				},
   154  				Spec: v1.PodSpec{
   155  					Containers: []v1.Container{
   156  						{
   157  							Image:                  "foo/bar",
   158  							TerminationMessagePath: v1.TerminationMessagePathDefault,
   159  							ImagePullPolicy:        v1.PullIfNotPresent,
   160  							SecurityContext:        securitycontext.ValidSecurityContextWithContainerDefaults(),
   161  						},
   162  					},
   163  					RestartPolicy: v1.RestartPolicyAlways,
   164  					DNSPolicy:     v1.DNSDefault,
   165  					NodeSelector: map[string]string{
   166  						"baz": "blah",
   167  					},
   168  				},
   169  			},
   170  		},
   171  	}
   172  }
   173  
   174  func TestControllerExpectations(t *testing.T) {
   175  	logger, _ := ktesting.NewTestContext(t)
   176  	ttl := 30 * time.Second
   177  	e, fakeClock := NewFakeControllerExpectationsLookup(ttl)
   178  	// In practice we can't really have add and delete expectations since we only either create or
   179  	// delete replicas in one rc pass, and the rc goes to sleep soon after until the expectations are
   180  	// either fulfilled or timeout.
   181  	adds, dels := 10, 30
   182  	rc := newReplicationController(1)
   183  
   184  	// RC fires off adds and deletes at apiserver, then sets expectations
   185  	rcKey, err := KeyFunc(rc)
   186  	assert.NoError(t, err, "Couldn't get key for object %#v: %v", rc, err)
   187  
   188  	e.SetExpectations(logger, rcKey, adds, dels)
   189  	var wg sync.WaitGroup
   190  	for i := 0; i < adds+1; i++ {
   191  		wg.Add(1)
   192  		go func() {
   193  			// In prod this can happen either because of a failed create by the rc
   194  			// or after having observed a create via informer
   195  			e.CreationObserved(logger, rcKey)
   196  			wg.Done()
   197  		}()
   198  	}
   199  	wg.Wait()
   200  
   201  	// There are still delete expectations
   202  	assert.False(t, e.SatisfiedExpectations(logger, rcKey), "Rc will sync before expectations are met")
   203  
   204  	for i := 0; i < dels+1; i++ {
   205  		wg.Add(1)
   206  		go func() {
   207  			e.DeletionObserved(logger, rcKey)
   208  			wg.Done()
   209  		}()
   210  	}
   211  	wg.Wait()
   212  
   213  	tests := []struct {
   214  		name                      string
   215  		expectationsToSet         []int
   216  		expireExpectations        bool
   217  		wantPodExpectations       []int64
   218  		wantExpectationsSatisfied bool
   219  	}{
   220  		{
   221  			name:                      "Expectations have been surpassed",
   222  			expireExpectations:        false,
   223  			wantPodExpectations:       []int64{int64(-1), int64(-1)},
   224  			wantExpectationsSatisfied: true,
   225  		},
   226  		{
   227  			name:                      "Old expectations are cleared because of ttl",
   228  			expectationsToSet:         []int{1, 2},
   229  			expireExpectations:        true,
   230  			wantPodExpectations:       []int64{int64(1), int64(2)},
   231  			wantExpectationsSatisfied: false,
   232  		},
   233  	}
   234  
   235  	for _, test := range tests {
   236  		t.Run(test.name, func(t *testing.T) {
   237  			if len(test.expectationsToSet) > 0 {
   238  				e.SetExpectations(logger, rcKey, test.expectationsToSet[0], test.expectationsToSet[1])
   239  			}
   240  			podExp, exists, err := e.GetExpectations(rcKey)
   241  			assert.NoError(t, err, "Could not get expectations for rc, exists %v and err %v", exists, err)
   242  			assert.True(t, exists, "Could not get expectations for rc, exists %v and err %v", exists, err)
   243  
   244  			add, del := podExp.GetExpectations()
   245  			assert.Equal(t, test.wantPodExpectations[0], add, "Unexpected pod expectations %#v", podExp)
   246  			assert.Equal(t, test.wantPodExpectations[1], del, "Unexpected pod expectations %#v", podExp)
   247  			assert.Equal(t, test.wantExpectationsSatisfied, e.SatisfiedExpectations(logger, rcKey), "Expectations are met but the rc will not sync")
   248  
   249  			if test.expireExpectations {
   250  				fakeClock.Step(ttl + 1)
   251  				assert.True(t, e.SatisfiedExpectations(logger, rcKey), "Expectations should have expired but didn't")
   252  			}
   253  		})
   254  	}
   255  }
   256  
   257  func TestUIDExpectations(t *testing.T) {
   258  	logger, _ := ktesting.NewTestContext(t)
   259  	uidExp := NewUIDTrackingControllerExpectations(NewControllerExpectations())
   260  	type test struct {
   261  		name        string
   262  		numReplicas int
   263  	}
   264  
   265  	shuffleTests := func(tests []test) {
   266  		for i := range tests {
   267  			j := rand.Intn(i + 1)
   268  			tests[i], tests[j] = tests[j], tests[i]
   269  		}
   270  	}
   271  
   272  	getRcDataFrom := func(test test) (string, []string) {
   273  		rc := newReplicationController(test.numReplicas)
   274  
   275  		rcName := fmt.Sprintf("rc-%v", test.numReplicas)
   276  		rc.Name = rcName
   277  		rc.Spec.Selector[rcName] = rcName
   278  
   279  		podList := newPodList(nil, 5, v1.PodRunning, rc)
   280  		rcKey, err := KeyFunc(rc)
   281  		if err != nil {
   282  			t.Fatalf("Couldn't get key for object %#v: %v", rc, err)
   283  		}
   284  
   285  		rcPodNames := []string{}
   286  		for i := range podList.Items {
   287  			p := &podList.Items[i]
   288  			p.Name = fmt.Sprintf("%v-%v", p.Name, rc.Name)
   289  			rcPodNames = append(rcPodNames, PodKey(p))
   290  		}
   291  		uidExp.ExpectDeletions(logger, rcKey, rcPodNames)
   292  
   293  		return rcKey, rcPodNames
   294  	}
   295  
   296  	tests := []test{
   297  		{name: "Replication controller with 2 replicas", numReplicas: 2},
   298  		{name: "Replication controller with 1 replica", numReplicas: 1},
   299  		{name: "Replication controller with no replicas", numReplicas: 0},
   300  		{name: "Replication controller with 5 replicas", numReplicas: 5},
   301  	}
   302  
   303  	shuffleTests(tests)
   304  	for _, test := range tests {
   305  		t.Run(test.name, func(t *testing.T) {
   306  
   307  			rcKey, rcPodNames := getRcDataFrom(test)
   308  			assert.False(t, uidExp.SatisfiedExpectations(logger, rcKey),
   309  				"Controller %v satisfied expectations before deletion", rcKey)
   310  
   311  			for _, p := range rcPodNames {
   312  				uidExp.DeletionObserved(logger, rcKey, p)
   313  			}
   314  
   315  			assert.True(t, uidExp.SatisfiedExpectations(logger, rcKey),
   316  				"Controller %v didn't satisfy expectations after deletion", rcKey)
   317  
   318  			uidExp.DeleteExpectations(logger, rcKey)
   319  
   320  			assert.Nil(t, uidExp.GetUIDs(rcKey),
   321  				"Failed to delete uid expectations for %v", rcKey)
   322  		})
   323  	}
   324  }
   325  
   326  func TestCreatePodsWithGenerateName(t *testing.T) {
   327  	ns := metav1.NamespaceDefault
   328  	generateName := "hello-"
   329  	controllerSpec := newReplicationController(1)
   330  	controllerRef := metav1.NewControllerRef(controllerSpec, v1.SchemeGroupVersion.WithKind("ReplicationController"))
   331  
   332  	type test struct {
   333  		name            string
   334  		podCreationFunc func(podControl RealPodControl) error
   335  		wantPod         *v1.Pod
   336  	}
   337  	var tests = []test{
   338  		{
   339  			name: "Create pod",
   340  			podCreationFunc: func(podControl RealPodControl) error {
   341  				return podControl.CreatePods(context.TODO(), ns, controllerSpec.Spec.Template, controllerSpec, controllerRef)
   342  			},
   343  			wantPod: &v1.Pod{
   344  				ObjectMeta: metav1.ObjectMeta{
   345  					Labels:       controllerSpec.Spec.Template.Labels,
   346  					GenerateName: fmt.Sprintf("%s-", controllerSpec.Name),
   347  				},
   348  				Spec: controllerSpec.Spec.Template.Spec,
   349  			},
   350  		},
   351  		{
   352  			name: "Create pod with generate name",
   353  			podCreationFunc: func(podControl RealPodControl) error {
   354  				// Make sure createReplica sends a POST to the apiserver with a pod from the controllers pod template
   355  				return podControl.CreatePodsWithGenerateName(context.TODO(), ns, controllerSpec.Spec.Template, controllerSpec, controllerRef, generateName)
   356  			},
   357  			wantPod: &v1.Pod{
   358  				ObjectMeta: metav1.ObjectMeta{
   359  					Labels:          controllerSpec.Spec.Template.Labels,
   360  					GenerateName:    generateName,
   361  					OwnerReferences: []metav1.OwnerReference{*controllerRef},
   362  				},
   363  				Spec: controllerSpec.Spec.Template.Spec,
   364  			},
   365  		},
   366  	}
   367  
   368  	for _, test := range tests {
   369  		t.Run(test.name, func(t *testing.T) {
   370  			body := runtime.EncodeOrDie(clientscheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "empty_pod"}})
   371  			fakeHandler := utiltesting.FakeHandler{
   372  				StatusCode:   200,
   373  				ResponseBody: string(body),
   374  			}
   375  			testServer := httptest.NewServer(&fakeHandler)
   376  			defer testServer.Close()
   377  			clientset := clientset.NewForConfigOrDie(&restclient.Config{Host: testServer.URL, ContentConfig: restclient.ContentConfig{GroupVersion: &schema.GroupVersion{Group: "", Version: "v1"}}})
   378  
   379  			podControl := RealPodControl{
   380  				KubeClient: clientset,
   381  				Recorder:   &record.FakeRecorder{},
   382  			}
   383  
   384  			err := test.podCreationFunc(podControl)
   385  			assert.NoError(t, err, "unexpected error: %v", err)
   386  
   387  			fakeHandler.ValidateRequest(t, "/api/v1/namespaces/default/pods", "POST", nil)
   388  			var actualPod = &v1.Pod{}
   389  			err = json.Unmarshal([]byte(fakeHandler.RequestBody), actualPod)
   390  			assert.NoError(t, err, "unexpected error: %v", err)
   391  			assert.True(t, apiequality.Semantic.DeepDerivative(test.wantPod, actualPod),
   392  				"Body: %s", fakeHandler.RequestBody)
   393  		})
   394  	}
   395  }
   396  
   397  func TestDeletePodsAllowsMissing(t *testing.T) {
   398  	fakeClient := fake.NewSimpleClientset()
   399  	podControl := RealPodControl{
   400  		KubeClient: fakeClient,
   401  		Recorder:   &record.FakeRecorder{},
   402  	}
   403  
   404  	controllerSpec := newReplicationController(1)
   405  
   406  	err := podControl.DeletePod(context.TODO(), "namespace-name", "podName", controllerSpec)
   407  	assert.True(t, apierrors.IsNotFound(err))
   408  }
   409  
   410  func TestCountTerminatingPods(t *testing.T) {
   411  	now := metav1.Now()
   412  
   413  	// This rc is not needed by the test, only the newPodList to give the pods labels/a namespace.
   414  	rc := newReplicationController(0)
   415  	podList := newPodList(nil, 7, v1.PodRunning, rc)
   416  	podList.Items[0].Status.Phase = v1.PodSucceeded
   417  	podList.Items[1].Status.Phase = v1.PodFailed
   418  	podList.Items[2].Status.Phase = v1.PodPending
   419  	podList.Items[2].SetDeletionTimestamp(&now)
   420  	podList.Items[3].Status.Phase = v1.PodRunning
   421  	podList.Items[3].SetDeletionTimestamp(&now)
   422  	var podPointers []*v1.Pod
   423  	for i := range podList.Items {
   424  		podPointers = append(podPointers, &podList.Items[i])
   425  	}
   426  
   427  	terminatingPods := CountTerminatingPods(podPointers)
   428  
   429  	assert.Equal(t, terminatingPods, int32(2))
   430  
   431  	terminatingList := FilterTerminatingPods(podPointers)
   432  	assert.Equal(t, len(terminatingList), int(2))
   433  }
   434  
   435  func TestActivePodFiltering(t *testing.T) {
   436  	logger, _ := ktesting.NewTestContext(t)
   437  	type podData struct {
   438  		podName  string
   439  		podPhase v1.PodPhase
   440  	}
   441  
   442  	type test struct {
   443  		name         string
   444  		pods         []podData
   445  		wantPodNames []string
   446  	}
   447  
   448  	tests := []test{
   449  		{
   450  			name: "Filters active pods",
   451  			pods: []podData{
   452  				{podName: "pod-1", podPhase: v1.PodSucceeded},
   453  				{podName: "pod-2", podPhase: v1.PodFailed},
   454  				{podName: "pod-3"},
   455  				{podName: "pod-4"},
   456  				{podName: "pod-5"},
   457  			},
   458  			wantPodNames: []string{"pod-3", "pod-4", "pod-5"},
   459  		},
   460  	}
   461  
   462  	for _, test := range tests {
   463  		t.Run(test.name, func(t *testing.T) {
   464  			// This rc is not needed by the test, only the newPodList to give the pods labels/a namespace.
   465  			rc := newReplicationController(0)
   466  			podList := newPodList(nil, 5, v1.PodRunning, rc)
   467  			for idx, testPod := range test.pods {
   468  				podList.Items[idx].Name = testPod.podName
   469  				podList.Items[idx].Status.Phase = testPod.podPhase
   470  			}
   471  
   472  			var podPointers []*v1.Pod
   473  			for i := range podList.Items {
   474  				podPointers = append(podPointers, &podList.Items[i])
   475  			}
   476  			got := FilterActivePods(logger, podPointers)
   477  			gotNames := sets.NewString()
   478  			for _, pod := range got {
   479  				gotNames.Insert(pod.Name)
   480  			}
   481  
   482  			if diff := cmp.Diff(test.wantPodNames, gotNames.List()); diff != "" {
   483  				t.Errorf("Active pod names (-want,+got):\n%s", diff)
   484  			}
   485  		})
   486  	}
   487  }
   488  
   489  func TestSortingActivePods(t *testing.T) {
   490  	now := metav1.Now()
   491  	then := metav1.Time{Time: now.AddDate(0, -1, 0)}
   492  
   493  	tests := []struct {
   494  		name      string
   495  		pods      []v1.Pod
   496  		wantOrder []string
   497  	}{
   498  		{
   499  			name: "Sorts by active pod",
   500  			pods: []v1.Pod{
   501  				{
   502  					ObjectMeta: metav1.ObjectMeta{Name: "unscheduled"},
   503  					Spec:       v1.PodSpec{NodeName: ""},
   504  					Status:     v1.PodStatus{Phase: v1.PodPending},
   505  				},
   506  				{
   507  					ObjectMeta: metav1.ObjectMeta{Name: "scheduledButPending"},
   508  					Spec:       v1.PodSpec{NodeName: "bar"},
   509  					Status:     v1.PodStatus{Phase: v1.PodPending},
   510  				},
   511  				{
   512  					ObjectMeta: metav1.ObjectMeta{Name: "unknownPhase"},
   513  					Spec:       v1.PodSpec{NodeName: "foo"},
   514  					Status:     v1.PodStatus{Phase: v1.PodUnknown},
   515  				},
   516  				{
   517  					ObjectMeta: metav1.ObjectMeta{Name: "runningButNotReady"},
   518  					Spec:       v1.PodSpec{NodeName: "foo"},
   519  					Status:     v1.PodStatus{Phase: v1.PodRunning},
   520  				},
   521  				{
   522  					ObjectMeta: metav1.ObjectMeta{Name: "runningNoLastTransitionTime"},
   523  					Spec:       v1.PodSpec{NodeName: "foo"},
   524  					Status: v1.PodStatus{
   525  						Phase:             v1.PodRunning,
   526  						Conditions:        []v1.PodCondition{{Type: v1.PodReady, Status: v1.ConditionTrue}},
   527  						ContainerStatuses: []v1.ContainerStatus{{RestartCount: 3}, {RestartCount: 0}},
   528  					},
   529  				},
   530  				{
   531  					ObjectMeta: metav1.ObjectMeta{Name: "runningWithLastTransitionTime"},
   532  					Spec:       v1.PodSpec{NodeName: "foo"},
   533  					Status: v1.PodStatus{
   534  						Phase:             v1.PodRunning,
   535  						Conditions:        []v1.PodCondition{{Type: v1.PodReady, Status: v1.ConditionTrue, LastTransitionTime: now}},
   536  						ContainerStatuses: []v1.ContainerStatus{{RestartCount: 3}, {RestartCount: 0}},
   537  					},
   538  				},
   539  				{
   540  					ObjectMeta: metav1.ObjectMeta{Name: "runningLongerTime"},
   541  					Spec:       v1.PodSpec{NodeName: "foo"},
   542  					Status: v1.PodStatus{
   543  						Phase:             v1.PodRunning,
   544  						Conditions:        []v1.PodCondition{{Type: v1.PodReady, Status: v1.ConditionTrue, LastTransitionTime: then}},
   545  						ContainerStatuses: []v1.ContainerStatus{{RestartCount: 3}, {RestartCount: 0}},
   546  					},
   547  				},
   548  				{
   549  					ObjectMeta: metav1.ObjectMeta{Name: "lowerContainerRestartCount", CreationTimestamp: now},
   550  					Spec:       v1.PodSpec{NodeName: "foo"},
   551  					Status: v1.PodStatus{
   552  						Phase:             v1.PodRunning,
   553  						Conditions:        []v1.PodCondition{{Type: v1.PodReady, Status: v1.ConditionTrue, LastTransitionTime: then}},
   554  						ContainerStatuses: []v1.ContainerStatus{{RestartCount: 2}, {RestartCount: 1}},
   555  					},
   556  				},
   557  				{
   558  					ObjectMeta: metav1.ObjectMeta{Name: "oldest", CreationTimestamp: then},
   559  					Spec:       v1.PodSpec{NodeName: "foo"},
   560  					Status: v1.PodStatus{
   561  						Phase:             v1.PodRunning,
   562  						Conditions:        []v1.PodCondition{{Type: v1.PodReady, Status: v1.ConditionTrue, LastTransitionTime: then}},
   563  						ContainerStatuses: []v1.ContainerStatus{{RestartCount: 2}, {RestartCount: 1}},
   564  					},
   565  				},
   566  			},
   567  			wantOrder: []string{
   568  				"unscheduled",
   569  				"scheduledButPending",
   570  				"unknownPhase",
   571  				"runningButNotReady",
   572  				"runningNoLastTransitionTime",
   573  				"runningWithLastTransitionTime",
   574  				"runningLongerTime",
   575  				"lowerContainerRestartCount",
   576  				"oldest",
   577  			},
   578  		},
   579  	}
   580  
   581  	for _, test := range tests {
   582  		t.Run(test.name, func(t *testing.T) {
   583  			numPods := len(test.pods)
   584  
   585  			for i := 0; i < 20; i++ {
   586  				idx := rand.Perm(numPods)
   587  				randomizedPods := make([]*v1.Pod, numPods)
   588  				for j := 0; j < numPods; j++ {
   589  					randomizedPods[j] = &test.pods[idx[j]]
   590  				}
   591  
   592  				sort.Sort(ActivePods(randomizedPods))
   593  				gotOrder := make([]string, len(randomizedPods))
   594  				for i := range randomizedPods {
   595  					gotOrder[i] = randomizedPods[i].Name
   596  				}
   597  
   598  				if diff := cmp.Diff(test.wantOrder, gotOrder); diff != "" {
   599  					t.Errorf("Sorted active pod names (-want,+got):\n%s", diff)
   600  				}
   601  			}
   602  		})
   603  	}
   604  }
   605  
   606  func TestSortingActivePodsWithRanks(t *testing.T) {
   607  	now := metav1.Now()
   608  	then1Month := metav1.Time{Time: now.AddDate(0, -1, 0)}
   609  	then2Hours := metav1.Time{Time: now.Add(-2 * time.Hour)}
   610  	then5Hours := metav1.Time{Time: now.Add(-5 * time.Hour)}
   611  	then8Hours := metav1.Time{Time: now.Add(-8 * time.Hour)}
   612  	zeroTime := metav1.Time{}
   613  	pod := func(podName, nodeName string, phase v1.PodPhase, ready bool, restarts int32, readySince metav1.Time, created metav1.Time, annotations map[string]string) *v1.Pod {
   614  		var conditions []v1.PodCondition
   615  		var containerStatuses []v1.ContainerStatus
   616  		if ready {
   617  			conditions = []v1.PodCondition{{Type: v1.PodReady, Status: v1.ConditionTrue, LastTransitionTime: readySince}}
   618  			containerStatuses = []v1.ContainerStatus{{RestartCount: restarts}}
   619  		}
   620  		return &v1.Pod{
   621  			ObjectMeta: metav1.ObjectMeta{
   622  				CreationTimestamp: created,
   623  				Name:              podName,
   624  				Annotations:       annotations,
   625  			},
   626  			Spec: v1.PodSpec{NodeName: nodeName},
   627  			Status: v1.PodStatus{
   628  				Conditions:        conditions,
   629  				ContainerStatuses: containerStatuses,
   630  				Phase:             phase,
   631  			},
   632  		}
   633  	}
   634  	var (
   635  		unscheduledPod                      = pod("unscheduled", "", v1.PodPending, false, 0, zeroTime, zeroTime, nil)
   636  		scheduledPendingPod                 = pod("pending", "node", v1.PodPending, false, 0, zeroTime, zeroTime, nil)
   637  		unknownPhasePod                     = pod("unknown-phase", "node", v1.PodUnknown, false, 0, zeroTime, zeroTime, nil)
   638  		runningNotReadyPod                  = pod("not-ready", "node", v1.PodRunning, false, 0, zeroTime, zeroTime, nil)
   639  		runningReadyNoLastTransitionTimePod = pod("ready-no-last-transition-time", "node", v1.PodRunning, true, 0, zeroTime, zeroTime, nil)
   640  		runningReadyNow                     = pod("ready-now", "node", v1.PodRunning, true, 0, now, now, nil)
   641  		runningReadyThen                    = pod("ready-then", "node", v1.PodRunning, true, 0, then1Month, then1Month, nil)
   642  		runningReadyNowHighRestarts         = pod("ready-high-restarts", "node", v1.PodRunning, true, 9001, now, now, nil)
   643  		runningReadyNowCreatedThen          = pod("ready-now-created-then", "node", v1.PodRunning, true, 0, now, then1Month, nil)
   644  		lowPodDeletionCost                  = pod("low-deletion-cost", "node", v1.PodRunning, true, 0, now, then1Month, map[string]string{core.PodDeletionCost: "10"})
   645  		highPodDeletionCost                 = pod("high-deletion-cost", "node", v1.PodRunning, true, 0, now, then1Month, map[string]string{core.PodDeletionCost: "100"})
   646  		unscheduled5Hours                   = pod("unscheduled-5-hours", "", v1.PodPending, false, 0, then5Hours, then5Hours, nil)
   647  		unscheduled8Hours                   = pod("unscheduled-10-hours", "", v1.PodPending, false, 0, then8Hours, then8Hours, nil)
   648  		ready2Hours                         = pod("ready-2-hours", "", v1.PodRunning, true, 0, then2Hours, then1Month, nil)
   649  		ready5Hours                         = pod("ready-5-hours", "", v1.PodRunning, true, 0, then5Hours, then1Month, nil)
   650  		ready10Hours                        = pod("ready-10-hours", "", v1.PodRunning, true, 0, then8Hours, then1Month, nil)
   651  	)
   652  	equalityTests := []struct {
   653  		p1                          *v1.Pod
   654  		p2                          *v1.Pod
   655  		disableLogarithmicScaleDown bool
   656  	}{
   657  		{p1: unscheduledPod},
   658  		{p1: scheduledPendingPod},
   659  		{p1: unknownPhasePod},
   660  		{p1: runningNotReadyPod},
   661  		{p1: runningReadyNowCreatedThen},
   662  		{p1: runningReadyNow},
   663  		{p1: runningReadyThen},
   664  		{p1: runningReadyNowHighRestarts},
   665  		{p1: runningReadyNowCreatedThen},
   666  		{p1: unscheduled5Hours, p2: unscheduled8Hours},
   667  		{p1: ready5Hours, p2: ready10Hours},
   668  	}
   669  	for i, test := range equalityTests {
   670  		t.Run(fmt.Sprintf("Equality tests %d", i), func(t *testing.T) {
   671  			defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.LogarithmicScaleDown, !test.disableLogarithmicScaleDown)()
   672  			if test.p2 == nil {
   673  				test.p2 = test.p1
   674  			}
   675  			podsWithRanks := ActivePodsWithRanks{
   676  				Pods: []*v1.Pod{test.p1, test.p2},
   677  				Rank: []int{1, 1},
   678  				Now:  now,
   679  			}
   680  			if podsWithRanks.Less(0, 1) || podsWithRanks.Less(1, 0) {
   681  				t.Errorf("expected pod %q to be equivalent to %q", test.p1.Name, test.p2.Name)
   682  			}
   683  		})
   684  	}
   685  
   686  	type podWithRank struct {
   687  		pod  *v1.Pod
   688  		rank int
   689  	}
   690  	inequalityTests := []struct {
   691  		lesser, greater             podWithRank
   692  		disablePodDeletioncost      bool
   693  		disableLogarithmicScaleDown bool
   694  	}{
   695  		{lesser: podWithRank{unscheduledPod, 1}, greater: podWithRank{scheduledPendingPod, 2}},
   696  		{lesser: podWithRank{unscheduledPod, 2}, greater: podWithRank{scheduledPendingPod, 1}},
   697  		{lesser: podWithRank{scheduledPendingPod, 1}, greater: podWithRank{unknownPhasePod, 2}},
   698  		{lesser: podWithRank{unknownPhasePod, 1}, greater: podWithRank{runningNotReadyPod, 2}},
   699  		{lesser: podWithRank{runningNotReadyPod, 1}, greater: podWithRank{runningReadyNoLastTransitionTimePod, 1}},
   700  		{lesser: podWithRank{runningReadyNoLastTransitionTimePod, 1}, greater: podWithRank{runningReadyNow, 1}},
   701  		{lesser: podWithRank{runningReadyNow, 2}, greater: podWithRank{runningReadyNoLastTransitionTimePod, 1}},
   702  		{lesser: podWithRank{runningReadyNow, 1}, greater: podWithRank{runningReadyThen, 1}},
   703  		{lesser: podWithRank{runningReadyNow, 2}, greater: podWithRank{runningReadyThen, 1}},
   704  		{lesser: podWithRank{runningReadyNowHighRestarts, 1}, greater: podWithRank{runningReadyNow, 1}},
   705  		{lesser: podWithRank{runningReadyNow, 2}, greater: podWithRank{runningReadyNowHighRestarts, 1}},
   706  		{lesser: podWithRank{runningReadyNow, 1}, greater: podWithRank{runningReadyNowCreatedThen, 1}},
   707  		{lesser: podWithRank{runningReadyNowCreatedThen, 2}, greater: podWithRank{runningReadyNow, 1}},
   708  		{lesser: podWithRank{lowPodDeletionCost, 2}, greater: podWithRank{highPodDeletionCost, 1}},
   709  		{lesser: podWithRank{highPodDeletionCost, 2}, greater: podWithRank{lowPodDeletionCost, 1}, disablePodDeletioncost: true},
   710  		{lesser: podWithRank{ready2Hours, 1}, greater: podWithRank{ready5Hours, 1}},
   711  	}
   712  
   713  	for i, test := range inequalityTests {
   714  		t.Run(fmt.Sprintf("Inequality tests %d", i), func(t *testing.T) {
   715  			defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodDeletionCost, !test.disablePodDeletioncost)()
   716  			defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.LogarithmicScaleDown, !test.disableLogarithmicScaleDown)()
   717  
   718  			podsWithRanks := ActivePodsWithRanks{
   719  				Pods: []*v1.Pod{test.lesser.pod, test.greater.pod},
   720  				Rank: []int{test.lesser.rank, test.greater.rank},
   721  				Now:  now,
   722  			}
   723  			if !podsWithRanks.Less(0, 1) {
   724  				t.Errorf("expected pod %q with rank %v to be less than %q with rank %v", podsWithRanks.Pods[0].Name, podsWithRanks.Rank[0], podsWithRanks.Pods[1].Name, podsWithRanks.Rank[1])
   725  			}
   726  			if podsWithRanks.Less(1, 0) {
   727  				t.Errorf("expected pod %q with rank %v not to be less than %v with rank %v", podsWithRanks.Pods[1].Name, podsWithRanks.Rank[1], podsWithRanks.Pods[0].Name, podsWithRanks.Rank[0])
   728  			}
   729  		})
   730  	}
   731  }
   732  
   733  func TestActiveReplicaSetsFiltering(t *testing.T) {
   734  
   735  	rsUuid := uuid.NewUUID()
   736  	tests := []struct {
   737  		name            string
   738  		replicaSets     []*apps.ReplicaSet
   739  		wantReplicaSets []*apps.ReplicaSet
   740  	}{
   741  		{
   742  			name: "Filters active replica sets",
   743  			replicaSets: []*apps.ReplicaSet{
   744  				newReplicaSet("zero", 0, rsUuid),
   745  				nil,
   746  				newReplicaSet("foo", 1, rsUuid),
   747  				newReplicaSet("bar", 2, rsUuid),
   748  			},
   749  			wantReplicaSets: []*apps.ReplicaSet{
   750  				newReplicaSet("foo", 1, rsUuid),
   751  				newReplicaSet("bar", 2, rsUuid),
   752  			},
   753  		},
   754  	}
   755  
   756  	for _, test := range tests {
   757  		t.Run(test.name, func(t *testing.T) {
   758  			gotReplicaSets := FilterActiveReplicaSets(test.replicaSets)
   759  
   760  			if diff := cmp.Diff(test.wantReplicaSets, gotReplicaSets); diff != "" {
   761  				t.Errorf("Active replica set names (-want,+got):\n%s", diff)
   762  			}
   763  		})
   764  	}
   765  }
   766  
   767  func TestComputeHash(t *testing.T) {
   768  	collisionCount := int32(1)
   769  	otherCollisionCount := int32(2)
   770  	maxCollisionCount := int32(math.MaxInt32)
   771  	tests := []struct {
   772  		name                string
   773  		template            *v1.PodTemplateSpec
   774  		collisionCount      *int32
   775  		otherCollisionCount *int32
   776  	}{
   777  		{
   778  			name:                "simple",
   779  			template:            &v1.PodTemplateSpec{},
   780  			collisionCount:      &collisionCount,
   781  			otherCollisionCount: &otherCollisionCount,
   782  		},
   783  		{
   784  			name:                "using math.MaxInt64",
   785  			template:            &v1.PodTemplateSpec{},
   786  			collisionCount:      nil,
   787  			otherCollisionCount: &maxCollisionCount,
   788  		},
   789  	}
   790  
   791  	for _, test := range tests {
   792  		hash := ComputeHash(test.template, test.collisionCount)
   793  		otherHash := ComputeHash(test.template, test.otherCollisionCount)
   794  
   795  		assert.NotEqual(t, hash, otherHash, "expected different hashes but got the same: %d", hash)
   796  	}
   797  }
   798  
   799  func TestRemoveTaintOffNode(t *testing.T) {
   800  	tests := []struct {
   801  		name           string
   802  		nodeHandler    *testutil.FakeNodeHandler
   803  		nodeName       string
   804  		taintsToRemove []*v1.Taint
   805  		expectedTaints []v1.Taint
   806  		requestCount   int
   807  	}{
   808  		{
   809  			name: "remove one taint from node",
   810  			nodeHandler: &testutil.FakeNodeHandler{
   811  				Existing: []*v1.Node{
   812  					{
   813  						ObjectMeta: metav1.ObjectMeta{
   814  							Name: "node1",
   815  						},
   816  						Spec: v1.NodeSpec{
   817  							Taints: []v1.Taint{
   818  								{Key: "key1", Value: "value1", Effect: "NoSchedule"},
   819  								{Key: "key2", Value: "value2", Effect: "NoExecute"},
   820  							},
   821  						},
   822  					},
   823  				},
   824  				Clientset: fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testutil.NewPod("pod0", "node0")}}),
   825  			},
   826  			nodeName: "node1",
   827  			taintsToRemove: []*v1.Taint{
   828  				{Key: "key2", Value: "value2", Effect: "NoExecute"},
   829  			},
   830  			expectedTaints: []v1.Taint{
   831  				{Key: "key1", Value: "value1", Effect: "NoSchedule"},
   832  			},
   833  			requestCount: 4,
   834  		},
   835  		{
   836  			name: "remove multiple taints from node",
   837  			nodeHandler: &testutil.FakeNodeHandler{
   838  				Existing: []*v1.Node{
   839  					{
   840  						ObjectMeta: metav1.ObjectMeta{
   841  							Name: "node1",
   842  						},
   843  						Spec: v1.NodeSpec{
   844  							Taints: []v1.Taint{
   845  								{Key: "key1", Value: "value1", Effect: "NoSchedule"},
   846  								{Key: "key2", Value: "value2", Effect: "NoExecute"},
   847  								{Key: "key3", Value: "value3", Effect: "NoSchedule"},
   848  								{Key: "key4", Value: "value4", Effect: "NoExecute"},
   849  							},
   850  						},
   851  					},
   852  				},
   853  				Clientset: fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testutil.NewPod("pod0", "node0")}}),
   854  			},
   855  			nodeName: "node1",
   856  			taintsToRemove: []*v1.Taint{
   857  				{Key: "key2", Value: "value2", Effect: "NoExecute"},
   858  				{Key: "key3", Value: "value3", Effect: "NoSchedule"},
   859  			},
   860  			expectedTaints: []v1.Taint{
   861  				{Key: "key1", Value: "value1", Effect: "NoSchedule"},
   862  				{Key: "key4", Value: "value4", Effect: "NoExecute"},
   863  			},
   864  			requestCount: 4,
   865  		},
   866  		{
   867  			name: "remove no-exist taints from node",
   868  			nodeHandler: &testutil.FakeNodeHandler{
   869  				Existing: []*v1.Node{
   870  					{
   871  						ObjectMeta: metav1.ObjectMeta{
   872  							Name: "node1",
   873  						},
   874  						Spec: v1.NodeSpec{
   875  							Taints: []v1.Taint{
   876  								{Key: "key1", Value: "value1", Effect: "NoSchedule"},
   877  								{Key: "key2", Value: "value2", Effect: "NoExecute"},
   878  							},
   879  						},
   880  					},
   881  				},
   882  				Clientset: fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testutil.NewPod("pod0", "node0")}}),
   883  			},
   884  			nodeName: "node1",
   885  			taintsToRemove: []*v1.Taint{
   886  				{Key: "key3", Value: "value3", Effect: "NoSchedule"},
   887  			},
   888  			expectedTaints: []v1.Taint{
   889  				{Key: "key1", Value: "value1", Effect: "NoSchedule"},
   890  				{Key: "key2", Value: "value2", Effect: "NoExecute"},
   891  			},
   892  			requestCount: 2,
   893  		},
   894  		{
   895  			name: "remove taint from node without taints",
   896  			nodeHandler: &testutil.FakeNodeHandler{
   897  				Existing: []*v1.Node{
   898  					{
   899  						ObjectMeta: metav1.ObjectMeta{
   900  							Name: "node1",
   901  						},
   902  					},
   903  				},
   904  				Clientset: fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testutil.NewPod("pod0", "node0")}}),
   905  			},
   906  			nodeName: "node1",
   907  			taintsToRemove: []*v1.Taint{
   908  				{Key: "key3", Value: "value3", Effect: "NoSchedule"},
   909  			},
   910  			expectedTaints: nil,
   911  			requestCount:   2,
   912  		},
   913  		{
   914  			name: "remove empty taint list from node without taints",
   915  			nodeHandler: &testutil.FakeNodeHandler{
   916  				Existing: []*v1.Node{
   917  					{
   918  						ObjectMeta: metav1.ObjectMeta{
   919  							Name: "node1",
   920  						},
   921  					},
   922  				},
   923  				Clientset: fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testutil.NewPod("pod0", "node0")}}),
   924  			},
   925  			nodeName:       "node1",
   926  			taintsToRemove: []*v1.Taint{},
   927  			expectedTaints: nil,
   928  			requestCount:   2,
   929  		},
   930  		{
   931  			name: "remove empty taint list from node",
   932  			nodeHandler: &testutil.FakeNodeHandler{
   933  				Existing: []*v1.Node{
   934  					{
   935  						ObjectMeta: metav1.ObjectMeta{
   936  							Name: "node1",
   937  						},
   938  						Spec: v1.NodeSpec{
   939  							Taints: []v1.Taint{
   940  								{Key: "key1", Value: "value1", Effect: "NoSchedule"},
   941  								{Key: "key2", Value: "value2", Effect: "NoExecute"},
   942  							},
   943  						},
   944  					},
   945  				},
   946  				Clientset: fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testutil.NewPod("pod0", "node0")}}),
   947  			},
   948  			nodeName:       "node1",
   949  			taintsToRemove: []*v1.Taint{},
   950  			expectedTaints: []v1.Taint{
   951  				{Key: "key1", Value: "value1", Effect: "NoSchedule"},
   952  				{Key: "key2", Value: "value2", Effect: "NoExecute"},
   953  			},
   954  			requestCount: 2,
   955  		},
   956  	}
   957  	for _, test := range tests {
   958  		node, _ := test.nodeHandler.Get(context.TODO(), test.nodeName, metav1.GetOptions{})
   959  		err := RemoveTaintOffNode(context.TODO(), test.nodeHandler, test.nodeName, node, test.taintsToRemove...)
   960  		assert.NoError(t, err, "%s: RemoveTaintOffNode() error = %v", test.name, err)
   961  
   962  		node, _ = test.nodeHandler.Get(context.TODO(), test.nodeName, metav1.GetOptions{})
   963  		assert.EqualValues(t, test.expectedTaints, node.Spec.Taints,
   964  			"%s: failed to remove taint off node: expected %+v, got %+v",
   965  			test.name, test.expectedTaints, node.Spec.Taints)
   966  
   967  		assert.Equal(t, test.requestCount, test.nodeHandler.RequestCount,
   968  			"%s: unexpected request count: expected %+v, got %+v",
   969  			test.name, test.requestCount, test.nodeHandler.RequestCount)
   970  	}
   971  }
   972  
   973  func TestAddOrUpdateTaintOnNode(t *testing.T) {
   974  	tests := []struct {
   975  		name           string
   976  		nodeHandler    *testutil.FakeNodeHandler
   977  		nodeName       string
   978  		taintsToAdd    []*v1.Taint
   979  		expectedTaints []v1.Taint
   980  		requestCount   int
   981  		expectedErr    error
   982  	}{
   983  		{
   984  			name: "add one taint on node",
   985  			nodeHandler: &testutil.FakeNodeHandler{
   986  				Existing: []*v1.Node{
   987  					{
   988  						ObjectMeta: metav1.ObjectMeta{
   989  							Name: "node1",
   990  						},
   991  						Spec: v1.NodeSpec{
   992  							Taints: []v1.Taint{
   993  								{Key: "key1", Value: "value1", Effect: "NoSchedule"},
   994  							},
   995  						},
   996  					},
   997  				},
   998  				Clientset: fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testutil.NewPod("pod0", "node0")}}),
   999  			},
  1000  			nodeName: "node1",
  1001  			taintsToAdd: []*v1.Taint{
  1002  				{Key: "key2", Value: "value2", Effect: "NoExecute"},
  1003  			},
  1004  			expectedTaints: []v1.Taint{
  1005  				{Key: "key1", Value: "value1", Effect: "NoSchedule"},
  1006  				{Key: "key2", Value: "value2", Effect: "NoExecute"},
  1007  			},
  1008  			requestCount: 3,
  1009  		},
  1010  		{
  1011  			name: "add multiple taints to node",
  1012  			nodeHandler: &testutil.FakeNodeHandler{
  1013  				Existing: []*v1.Node{
  1014  					{
  1015  						ObjectMeta: metav1.ObjectMeta{
  1016  							Name: "node1",
  1017  						},
  1018  						Spec: v1.NodeSpec{
  1019  							Taints: []v1.Taint{
  1020  								{Key: "key1", Value: "value1", Effect: "NoSchedule"},
  1021  								{Key: "key2", Value: "value2", Effect: "NoExecute"},
  1022  							},
  1023  						},
  1024  					},
  1025  				},
  1026  				Clientset: fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testutil.NewPod("pod0", "node0")}}),
  1027  			},
  1028  			nodeName: "node1",
  1029  			taintsToAdd: []*v1.Taint{
  1030  				{Key: "key3", Value: "value3", Effect: "NoSchedule"},
  1031  				{Key: "key4", Value: "value4", Effect: "NoExecute"},
  1032  			},
  1033  			expectedTaints: []v1.Taint{
  1034  				{Key: "key1", Value: "value1", Effect: "NoSchedule"},
  1035  				{Key: "key2", Value: "value2", Effect: "NoExecute"},
  1036  				{Key: "key3", Value: "value3", Effect: "NoSchedule"},
  1037  				{Key: "key4", Value: "value4", Effect: "NoExecute"},
  1038  			},
  1039  			requestCount: 3,
  1040  		},
  1041  		{
  1042  			name: "add exist taints to node",
  1043  			nodeHandler: &testutil.FakeNodeHandler{
  1044  				Existing: []*v1.Node{
  1045  					{
  1046  						ObjectMeta: metav1.ObjectMeta{
  1047  							Name: "node1",
  1048  						},
  1049  						Spec: v1.NodeSpec{
  1050  							Taints: []v1.Taint{
  1051  								{Key: "key1", Value: "value1", Effect: "NoSchedule"},
  1052  								{Key: "key2", Value: "value2", Effect: "NoExecute"},
  1053  							},
  1054  						},
  1055  					},
  1056  				},
  1057  				Clientset: fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testutil.NewPod("pod0", "node0")}}),
  1058  			},
  1059  			nodeName: "node1",
  1060  			taintsToAdd: []*v1.Taint{
  1061  				{Key: "key2", Value: "value2", Effect: "NoExecute"},
  1062  			},
  1063  			expectedTaints: []v1.Taint{
  1064  				{Key: "key1", Value: "value1", Effect: "NoSchedule"},
  1065  				{Key: "key2", Value: "value2", Effect: "NoExecute"},
  1066  			},
  1067  			requestCount: 2,
  1068  		},
  1069  		{
  1070  			name: "add taint to node without taints",
  1071  			nodeHandler: &testutil.FakeNodeHandler{
  1072  				Existing: []*v1.Node{
  1073  					{
  1074  						ObjectMeta: metav1.ObjectMeta{
  1075  							Name: "node1",
  1076  						},
  1077  					},
  1078  				},
  1079  				Clientset: fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testutil.NewPod("pod0", "node0")}}),
  1080  			},
  1081  			nodeName: "node1",
  1082  			taintsToAdd: []*v1.Taint{
  1083  				{Key: "key3", Value: "value3", Effect: "NoSchedule"},
  1084  			},
  1085  			expectedTaints: []v1.Taint{
  1086  				{Key: "key3", Value: "value3", Effect: "NoSchedule"},
  1087  			},
  1088  			requestCount: 3,
  1089  		},
  1090  		{
  1091  			name: "add empty taint list to node without taints",
  1092  			nodeHandler: &testutil.FakeNodeHandler{
  1093  				Existing: []*v1.Node{
  1094  					{
  1095  						ObjectMeta: metav1.ObjectMeta{
  1096  							Name: "node1",
  1097  						},
  1098  					},
  1099  				},
  1100  				Clientset: fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testutil.NewPod("pod0", "node0")}}),
  1101  			},
  1102  			nodeName:       "node1",
  1103  			taintsToAdd:    []*v1.Taint{},
  1104  			expectedTaints: nil,
  1105  			requestCount:   1,
  1106  		},
  1107  		{
  1108  			name: "add empty taint list to node",
  1109  			nodeHandler: &testutil.FakeNodeHandler{
  1110  				Existing: []*v1.Node{
  1111  					{
  1112  						ObjectMeta: metav1.ObjectMeta{
  1113  							Name: "node1",
  1114  						},
  1115  						Spec: v1.NodeSpec{
  1116  							Taints: []v1.Taint{
  1117  								{Key: "key1", Value: "value1", Effect: "NoSchedule"},
  1118  								{Key: "key2", Value: "value2", Effect: "NoExecute"},
  1119  							},
  1120  						},
  1121  					},
  1122  				},
  1123  				Clientset: fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testutil.NewPod("pod0", "node0")}}),
  1124  			},
  1125  			nodeName:    "node1",
  1126  			taintsToAdd: []*v1.Taint{},
  1127  			expectedTaints: []v1.Taint{
  1128  				{Key: "key1", Value: "value1", Effect: "NoSchedule"},
  1129  				{Key: "key2", Value: "value2", Effect: "NoExecute"},
  1130  			},
  1131  			requestCount: 1,
  1132  		},
  1133  		{
  1134  			name: "add taint to changed node",
  1135  			nodeHandler: &testutil.FakeNodeHandler{
  1136  				Existing: []*v1.Node{
  1137  					{
  1138  						ObjectMeta: metav1.ObjectMeta{
  1139  							Name:            "node1",
  1140  							ResourceVersion: "1",
  1141  						},
  1142  						Spec: v1.NodeSpec{
  1143  							Taints: []v1.Taint{
  1144  								{Key: "key1", Value: "value1", Effect: "NoSchedule"},
  1145  							},
  1146  						},
  1147  					},
  1148  				},
  1149  				Clientset: fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testutil.NewPod("pod0", "node0")}}),
  1150  				AsyncCalls: []func(*testutil.FakeNodeHandler){func(m *testutil.FakeNodeHandler) {
  1151  					if len(m.UpdatedNodes) == 0 {
  1152  						m.UpdatedNodes = append(m.UpdatedNodes, &v1.Node{
  1153  							ObjectMeta: metav1.ObjectMeta{
  1154  								Name:            "node1",
  1155  								ResourceVersion: "2",
  1156  							},
  1157  							Spec: v1.NodeSpec{
  1158  								Taints: []v1.Taint{},
  1159  							}})
  1160  					}
  1161  				}},
  1162  			},
  1163  			nodeName:    "node1",
  1164  			taintsToAdd: []*v1.Taint{{Key: "key2", Value: "value2", Effect: "NoExecute"}},
  1165  			expectedTaints: []v1.Taint{
  1166  				{Key: "key2", Value: "value2", Effect: "NoExecute"},
  1167  			},
  1168  			requestCount: 5,
  1169  		},
  1170  		{
  1171  			name: "add taint to non-exist node",
  1172  			nodeHandler: &testutil.FakeNodeHandler{
  1173  				Existing: []*v1.Node{
  1174  					{
  1175  						ObjectMeta: metav1.ObjectMeta{
  1176  							Name:            "node1",
  1177  							ResourceVersion: "1",
  1178  						},
  1179  						Spec: v1.NodeSpec{
  1180  							Taints: []v1.Taint{
  1181  								{Key: "key1", Value: "value1", Effect: "NoSchedule"},
  1182  							},
  1183  						},
  1184  					},
  1185  				},
  1186  				Clientset: fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testutil.NewPod("pod0", "node0")}}),
  1187  			},
  1188  			nodeName:    "node2",
  1189  			taintsToAdd: []*v1.Taint{{Key: "key2", Value: "value2", Effect: "NoExecute"}},
  1190  			expectedErr: apierrors.NewNotFound(schema.GroupResource{Resource: "nodes"}, "node2"),
  1191  		},
  1192  	}
  1193  	for _, test := range tests {
  1194  		err := AddOrUpdateTaintOnNode(context.TODO(), test.nodeHandler, test.nodeName, test.taintsToAdd...)
  1195  		if test.expectedErr != nil {
  1196  			assert.Equal(t, test.expectedErr, err, "AddOrUpdateTaintOnNode get unexpected error")
  1197  			continue
  1198  		}
  1199  		assert.NoError(t, err, "%s: AddOrUpdateTaintOnNode() error = %v", test.name, err)
  1200  
  1201  		node, _ := test.nodeHandler.Get(context.TODO(), test.nodeName, metav1.GetOptions{})
  1202  		assert.EqualValues(t, test.expectedTaints, node.Spec.Taints,
  1203  			"%s: failed to add taint to node: expected %+v, got %+v",
  1204  			test.name, test.expectedTaints, node.Spec.Taints)
  1205  
  1206  		assert.Equal(t, test.requestCount, test.nodeHandler.RequestCount,
  1207  			"%s: unexpected request count: expected %+v, got %+v",
  1208  			test.name, test.requestCount, test.nodeHandler.RequestCount)
  1209  	}
  1210  }
  1211  

View as plain text