Commit d25af7bb authored by Jun Xiang Tee's avatar Jun Xiang Tee

refactor replicaset sync call tree

parent 82184d8e
...@@ -43,7 +43,10 @@ go_library( ...@@ -43,7 +43,10 @@ go_library(
go_test( go_test(
name = "go_default_test", name = "go_default_test",
srcs = ["replica_set_test.go"], srcs = [
"replica_set_test.go",
"replica_set_utils_test.go",
],
importpath = "k8s.io/kubernetes/pkg/controller/replicaset", importpath = "k8s.io/kubernetes/pkg/controller/replicaset",
library = ":go_default_library", library = ":go_default_library",
deps = [ deps = [
......
...@@ -26,6 +26,7 @@ import ( ...@@ -26,6 +26,7 @@ import (
"net/url" "net/url"
"reflect" "reflect"
"strings" "strings"
"sync"
"testing" "testing"
"time" "time"
...@@ -1373,3 +1374,254 @@ func TestRemoveCondition(t *testing.T) { ...@@ -1373,3 +1374,254 @@ func TestRemoveCondition(t *testing.T) {
} }
} }
} }
func TestSlowStartBatch(t *testing.T) {
fakeErr := fmt.Errorf("fake error")
callCnt := 0
callLimit := 0
var lock sync.Mutex
fn := func() error {
lock.Lock()
defer lock.Unlock()
callCnt++
if callCnt > callLimit {
return fakeErr
}
return nil
}
tests := []struct {
name string
count int
callLimit int
fn func() error
expectedSuccesses int
expectedErr error
expectedCallCnt int
}{
{
name: "callLimit = 0 (all fail)",
count: 10,
callLimit: 0,
fn: fn,
expectedSuccesses: 0,
expectedErr: fakeErr,
expectedCallCnt: 1, // 1(first batch): function will be called at least once
},
{
name: "callLimit = count (all succeed)",
count: 10,
callLimit: 10,
fn: fn,
expectedSuccesses: 10,
expectedErr: nil,
expectedCallCnt: 10, // 1(first batch) + 2(2nd batch) + 4(3rd batch) + 3(4th batch) = 10
},
{
name: "callLimit < count (some succeed)",
count: 10,
callLimit: 5,
fn: fn,
expectedSuccesses: 5,
expectedErr: fakeErr,
expectedCallCnt: 7, // 1(first batch) + 2(2nd batch) + 4(3rd batch) = 7
},
}
for _, test := range tests {
callCnt = 0
callLimit = test.callLimit
successes, err := slowStartBatch(test.count, 1, test.fn)
if successes != test.expectedSuccesses {
t.Errorf("%s: unexpected processed batch size, expected %d, got %d", test.name, test.expectedSuccesses, successes)
}
if err != test.expectedErr {
t.Errorf("%s: unexpected processed batch size, expected %v, got %v", test.name, test.expectedErr, err)
}
// verify that slowStartBatch stops trying more calls after a batch fails
if callCnt != test.expectedCallCnt {
t.Errorf("%s: slowStartBatch() still tries calls after a batch fails, expected %d calls, got %d", test.name, test.expectedCallCnt, callCnt)
}
}
}
func TestGetPodsToDelete(t *testing.T) {
labelMap := map[string]string{"name": "foo"}
rs := newReplicaSet(1, labelMap)
// an unscheduled, pending pod
unscheduledPendingPod := newPod("unscheduled-pending-pod", rs, v1.PodPending, nil, true)
// a scheduled, pending pod
scheduledPendingPod := newPod("scheduled-pending-pod", rs, v1.PodPending, nil, true)
scheduledPendingPod.Spec.NodeName = "fake-node"
// a scheduled, running, not-ready pod
scheduledRunningNotReadyPod := newPod("scheduled-running-not-ready-pod", rs, v1.PodRunning, nil, true)
scheduledRunningNotReadyPod.Spec.NodeName = "fake-node"
scheduledRunningNotReadyPod.Status.Conditions = []v1.PodCondition{
{
Type: v1.PodReady,
Status: v1.ConditionFalse,
},
}
// a scheduled, running, ready pod
scheduledRunningReadyPod := newPod("scheduled-running-ready-pod", rs, v1.PodRunning, nil, true)
scheduledRunningReadyPod.Spec.NodeName = "fake-node"
scheduledRunningReadyPod.Status.Conditions = []v1.PodCondition{
{
Type: v1.PodReady,
Status: v1.ConditionTrue,
},
}
tests := []struct {
name string
pods []*v1.Pod
diff int
expectedPodsToDelete []*v1.Pod
}{
// Order used when selecting pods for deletion:
// an unscheduled, pending pod
// a scheduled, pending pod
// a scheduled, running, not-ready pod
// a scheduled, running, ready pod
// Note that a pending pod cannot be ready
{
"len(pods) = 0 (i.e., diff = 0 too)",
[]*v1.Pod{},
0,
[]*v1.Pod{},
},
{
"diff = len(pods)",
[]*v1.Pod{
scheduledRunningNotReadyPod,
scheduledRunningReadyPod,
},
2,
[]*v1.Pod{scheduledRunningNotReadyPod, scheduledRunningReadyPod},
},
{
"diff < len(pods)",
[]*v1.Pod{
scheduledRunningReadyPod,
scheduledRunningNotReadyPod,
},
1,
[]*v1.Pod{scheduledRunningNotReadyPod},
},
{
"various pod phases and conditions, diff = len(pods)",
[]*v1.Pod{
scheduledRunningReadyPod,
scheduledRunningNotReadyPod,
scheduledPendingPod,
unscheduledPendingPod,
},
4,
[]*v1.Pod{
scheduledRunningReadyPod,
scheduledRunningNotReadyPod,
scheduledPendingPod,
unscheduledPendingPod,
},
},
{
"scheduled vs unscheduled, diff < len(pods)",
[]*v1.Pod{
scheduledPendingPod,
unscheduledPendingPod,
},
1,
[]*v1.Pod{
unscheduledPendingPod,
},
},
{
"ready vs not-ready, diff < len(pods)",
[]*v1.Pod{
scheduledRunningReadyPod,
scheduledRunningNotReadyPod,
scheduledRunningNotReadyPod,
},
2,
[]*v1.Pod{
scheduledRunningNotReadyPod,
scheduledRunningNotReadyPod,
},
},
{
"pending vs running, diff < len(pods)",
[]*v1.Pod{
scheduledPendingPod,
scheduledRunningNotReadyPod,
},
1,
[]*v1.Pod{
scheduledPendingPod,
},
},
{
"various pod phases and conditions, diff < len(pods)",
[]*v1.Pod{
scheduledRunningReadyPod,
scheduledRunningNotReadyPod,
scheduledPendingPod,
unscheduledPendingPod,
},
3,
[]*v1.Pod{
unscheduledPendingPod,
scheduledPendingPod,
scheduledRunningNotReadyPod,
},
},
}
for _, test := range tests {
podsToDelete := getPodsToDelete(test.pods, test.diff)
if len(podsToDelete) != len(test.expectedPodsToDelete) {
t.Errorf("%s: unexpected pods to delete, expected %v, got %v", test.name, test.expectedPodsToDelete, podsToDelete)
}
if !reflect.DeepEqual(podsToDelete, test.expectedPodsToDelete) {
t.Errorf("%s: unexpected pods to delete, expected %v, got %v", test.name, test.expectedPodsToDelete, podsToDelete)
}
}
}
func TestGetPodKeys(t *testing.T) {
labelMap := map[string]string{"name": "foo"}
rs := newReplicaSet(1, labelMap)
pod1 := newPod("pod1", rs, v1.PodRunning, nil, true)
pod2 := newPod("pod2", rs, v1.PodRunning, nil, true)
tests := []struct {
name string
pods []*v1.Pod
expectedPodKeys []string
}{
{
"len(pods) = 0 (i.e., pods = nil)",
[]*v1.Pod{},
[]string{},
},
{
"len(pods) > 0",
[]*v1.Pod{
pod1,
pod2,
},
[]string{"default/pod1", "default/pod2"},
},
}
for _, test := range tests {
podKeys := getPodKeys(test.pods)
if len(podKeys) != len(test.expectedPodKeys) {
t.Errorf("%s: unexpected keys for pods to delete, expected %v, got %v", test.name, test.expectedPodKeys, podKeys)
}
for i := 0; i < len(podKeys); i++ {
if podKeys[i] != test.expectedPodKeys[i] {
t.Errorf("%s: unexpected keys for pods to delete, expected %v, got %v", test.name, test.expectedPodKeys, podKeys)
}
}
}
}
/*
Copyright 2017 The Kubernetes Authors.
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.
*/
// If you make changes to this file, you should also make the corresponding change in ReplicationController.
package replicaset
import (
"fmt"
"reflect"
"testing"
"k8s.io/api/core/v1"
extensions "k8s.io/api/extensions/v1beta1"
)
func TestCalculateStatus(t *testing.T) {
labelMap := map[string]string{"name": "foo"}
fullLabelMap := map[string]string{"name": "foo", "type": "production"}
notFullyLabelledRS := newReplicaSet(1, labelMap)
// Set replica num to 2 for status condition testing (diff < 0, diff > 0)
fullyLabelledRS := newReplicaSet(2, fullLabelMap)
longMinReadySecondsRS := newReplicaSet(1, fullLabelMap)
longMinReadySecondsRS.Spec.MinReadySeconds = 3600
rsStatusTests := []struct {
name string
replicaset *extensions.ReplicaSet
filteredPods []*v1.Pod
expectedReplicaSetStatus extensions.ReplicaSetStatus
}{
{
"1 fully labelled pod",
fullyLabelledRS,
[]*v1.Pod{
newPod("pod1", fullyLabelledRS, v1.PodRunning, nil, true),
},
extensions.ReplicaSetStatus{
Replicas: 1,
FullyLabeledReplicas: 1,
ReadyReplicas: 1,
AvailableReplicas: 1,
},
},
{
"1 not fully labelled pod",
notFullyLabelledRS,
[]*v1.Pod{
newPod("pod1", notFullyLabelledRS, v1.PodRunning, nil, true),
},
extensions.ReplicaSetStatus{
Replicas: 1,
FullyLabeledReplicas: 0,
ReadyReplicas: 1,
AvailableReplicas: 1,
},
},
{
"2 fully labelled pods",
fullyLabelledRS,
[]*v1.Pod{
newPod("pod1", fullyLabelledRS, v1.PodRunning, nil, true),
newPod("pod2", fullyLabelledRS, v1.PodRunning, nil, true),
},
extensions.ReplicaSetStatus{
Replicas: 2,
FullyLabeledReplicas: 2,
ReadyReplicas: 2,
AvailableReplicas: 2,
},
},
{
"2 not fully labelled pods",
notFullyLabelledRS,
[]*v1.Pod{
newPod("pod1", notFullyLabelledRS, v1.PodRunning, nil, true),
newPod("pod2", notFullyLabelledRS, v1.PodRunning, nil, true),
},
extensions.ReplicaSetStatus{
Replicas: 2,
FullyLabeledReplicas: 0,
ReadyReplicas: 2,
AvailableReplicas: 2,
},
},
{
"1 fully labelled pod, 1 not fully labelled pod",
notFullyLabelledRS,
[]*v1.Pod{
newPod("pod1", notFullyLabelledRS, v1.PodRunning, nil, true),
newPod("pod2", fullyLabelledRS, v1.PodRunning, nil, true),
},
extensions.ReplicaSetStatus{
Replicas: 2,
FullyLabeledReplicas: 1,
ReadyReplicas: 2,
AvailableReplicas: 2,
},
},
{
"1 non-ready pod",
fullyLabelledRS,
[]*v1.Pod{
newPod("pod1", fullyLabelledRS, v1.PodPending, nil, true),
},
extensions.ReplicaSetStatus{
Replicas: 1,
FullyLabeledReplicas: 1,
ReadyReplicas: 0,
AvailableReplicas: 0,
},
},
{
"1 ready but non-available pod",
longMinReadySecondsRS,
[]*v1.Pod{
newPod("pod1", longMinReadySecondsRS, v1.PodRunning, nil, true),
},
extensions.ReplicaSetStatus{
Replicas: 1,
FullyLabeledReplicas: 1,
ReadyReplicas: 1,
AvailableReplicas: 0,
},
},
}
for _, test := range rsStatusTests {
replicaSetStatus := calculateStatus(test.replicaset, test.filteredPods, nil)
if !reflect.DeepEqual(replicaSetStatus, test.expectedReplicaSetStatus) {
t.Errorf("%s: unexpected replicaset status: expected %v, got %v", test.name, test.expectedReplicaSetStatus, replicaSetStatus)
}
}
}
func TestCalculateStatusConditions(t *testing.T) {
labelMap := map[string]string{"name": "foo"}
rs := newReplicaSet(2, labelMap)
replicaFailureRS := newReplicaSet(10, labelMap)
replicaFailureRS.Status.Conditions = []extensions.ReplicaSetCondition{
{
Type: extensions.ReplicaSetReplicaFailure,
Status: v1.ConditionTrue,
},
}
rsStatusConditionTests := []struct {
name string
replicaset *extensions.ReplicaSet
filteredPods []*v1.Pod
manageReplicasErr error
expectedReplicaSetConditions []extensions.ReplicaSetCondition
}{
{
"manageReplicasErr != nil && failureCond == nil, diff < 0",
rs,
[]*v1.Pod{
newPod("pod1", rs, v1.PodRunning, nil, true),
},
fmt.Errorf("fake manageReplicasErr"),
[]extensions.ReplicaSetCondition{
{
Type: extensions.ReplicaSetReplicaFailure,
Status: v1.ConditionTrue,
Reason: "FailedCreate",
Message: "fake manageReplicasErr",
},
},
},
{
"manageReplicasErr != nil && failureCond == nil, diff > 0",
rs,
[]*v1.Pod{
newPod("pod1", rs, v1.PodRunning, nil, true),
newPod("pod2", rs, v1.PodRunning, nil, true),
newPod("pod3", rs, v1.PodRunning, nil, true),
},
fmt.Errorf("fake manageReplicasErr"),
[]extensions.ReplicaSetCondition{
{
Type: extensions.ReplicaSetReplicaFailure,
Status: v1.ConditionTrue,
Reason: "FailedDelete",
Message: "fake manageReplicasErr",
},
},
},
{
"manageReplicasErr == nil && failureCond != nil",
replicaFailureRS,
[]*v1.Pod{
newPod("pod1", replicaFailureRS, v1.PodRunning, nil, true),
},
nil,
nil,
},
{
"manageReplicasErr != nil && failureCond != nil",
replicaFailureRS,
[]*v1.Pod{
newPod("pod1", replicaFailureRS, v1.PodRunning, nil, true),
},
fmt.Errorf("fake manageReplicasErr"),
[]extensions.ReplicaSetCondition{
{
Type: extensions.ReplicaSetReplicaFailure,
Status: v1.ConditionTrue,
},
},
},
{
"manageReplicasErr == nil && failureCond == nil",
rs,
[]*v1.Pod{
newPod("pod1", rs, v1.PodRunning, nil, true),
},
nil,
nil,
},
}
for _, test := range rsStatusConditionTests {
replicaSetStatus := calculateStatus(test.replicaset, test.filteredPods, test.manageReplicasErr)
// all test cases have at most 1 status condition
if len(replicaSetStatus.Conditions) > 0 {
test.expectedReplicaSetConditions[0].LastTransitionTime = replicaSetStatus.Conditions[0].LastTransitionTime
}
if !reflect.DeepEqual(replicaSetStatus.Conditions, test.expectedReplicaSetConditions) {
t.Errorf("%s: unexpected replicaset status: expected %v, got %v", test.name, test.expectedReplicaSetConditions, replicaSetStatus.Conditions)
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment