mirror of
				https://github.com/k3s-io/kubernetes.git
				synced 2025-11-03 23:40:03 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			1852 lines
		
	
	
		
			54 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			1852 lines
		
	
	
		
			54 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
/*
 | 
						|
Copyright 2014 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.
 | 
						|
*/
 | 
						|
 | 
						|
package kubectl
 | 
						|
 | 
						|
import (
 | 
						|
	"bytes"
 | 
						|
	"fmt"
 | 
						|
	"io"
 | 
						|
	"io/ioutil"
 | 
						|
	"net/http"
 | 
						|
	"reflect"
 | 
						|
	"testing"
 | 
						|
	"time"
 | 
						|
 | 
						|
	corev1 "k8s.io/api/core/v1"
 | 
						|
	apiequality "k8s.io/apimachinery/pkg/api/equality"
 | 
						|
	"k8s.io/apimachinery/pkg/api/errors"
 | 
						|
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
						|
	"k8s.io/apimachinery/pkg/runtime"
 | 
						|
	"k8s.io/apimachinery/pkg/runtime/schema"
 | 
						|
	"k8s.io/apimachinery/pkg/util/diff"
 | 
						|
	"k8s.io/apimachinery/pkg/util/intstr"
 | 
						|
	"k8s.io/apimachinery/pkg/util/sets"
 | 
						|
	"k8s.io/client-go/kubernetes"
 | 
						|
	"k8s.io/client-go/kubernetes/fake"
 | 
						|
	restclient "k8s.io/client-go/rest"
 | 
						|
	manualfake "k8s.io/client-go/rest/fake"
 | 
						|
	testcore "k8s.io/client-go/testing"
 | 
						|
	"k8s.io/kubernetes/pkg/kubectl/scheme"
 | 
						|
	"k8s.io/kubernetes/pkg/kubectl/util"
 | 
						|
)
 | 
						|
 | 
						|
func oldRc(replicas int, original int) *corev1.ReplicationController {
 | 
						|
	t := replicas
 | 
						|
	replicasCopy := int32(t)
 | 
						|
	return &corev1.ReplicationController{
 | 
						|
		ObjectMeta: metav1.ObjectMeta{
 | 
						|
			Namespace: metav1.NamespaceDefault,
 | 
						|
			Name:      "foo-v1",
 | 
						|
			UID:       "7764ae47-9092-11e4-8393-42010af018ff",
 | 
						|
			Annotations: map[string]string{
 | 
						|
				originalReplicasAnnotation: fmt.Sprintf("%d", original),
 | 
						|
			},
 | 
						|
		},
 | 
						|
		Spec: corev1.ReplicationControllerSpec{
 | 
						|
			Replicas: &replicasCopy,
 | 
						|
			Selector: map[string]string{"version": "v1"},
 | 
						|
			Template: &corev1.PodTemplateSpec{
 | 
						|
				ObjectMeta: metav1.ObjectMeta{
 | 
						|
					Name:   "foo-v1",
 | 
						|
					Labels: map[string]string{"version": "v1"},
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		Status: corev1.ReplicationControllerStatus{
 | 
						|
			Replicas: int32(replicas),
 | 
						|
		},
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func newRc(replicas int, desired int) *corev1.ReplicationController {
 | 
						|
	rc := oldRc(replicas, replicas)
 | 
						|
	rc.Spec.Template = &corev1.PodTemplateSpec{
 | 
						|
		ObjectMeta: metav1.ObjectMeta{
 | 
						|
			Name:   "foo-v2",
 | 
						|
			Labels: map[string]string{"version": "v2"},
 | 
						|
		},
 | 
						|
	}
 | 
						|
	rc.Spec.Selector = map[string]string{"version": "v2"}
 | 
						|
	rc.ObjectMeta = metav1.ObjectMeta{
 | 
						|
		Namespace: metav1.NamespaceDefault,
 | 
						|
		Name:      "foo-v2",
 | 
						|
		Annotations: map[string]string{
 | 
						|
			desiredReplicasAnnotation: fmt.Sprintf("%d", desired),
 | 
						|
			sourceIDAnnotation:        "foo-v1:7764ae47-9092-11e4-8393-42010af018ff",
 | 
						|
		},
 | 
						|
	}
 | 
						|
	return rc
 | 
						|
}
 | 
						|
 | 
						|
// TestUpdate performs complex scenario testing for rolling updates. It
 | 
						|
// provides fine grained control over the states for each update interval to
 | 
						|
// allow the expression of as many edge cases as possible.
 | 
						|
func TestUpdate(t *testing.T) {
 | 
						|
	// up represents a simulated scale up event and expectation
 | 
						|
	type up struct {
 | 
						|
		// to is the expected replica count for a scale-up
 | 
						|
		to int
 | 
						|
	}
 | 
						|
	// down represents a simulated scale down event and expectation
 | 
						|
	type down struct {
 | 
						|
		// oldReady is the number of oldRc replicas which will be seen
 | 
						|
		// as ready during the scale down attempt
 | 
						|
		oldReady int
 | 
						|
		// newReady is the number of newRc replicas which will be seen
 | 
						|
		// as ready during the scale up attempt
 | 
						|
		newReady int
 | 
						|
		// to is the expected replica count for the scale down
 | 
						|
		to int
 | 
						|
		// noop and to are mutually exclusive; if noop is true, that means for
 | 
						|
		// this down event, no scaling attempt should be made (for example, if
 | 
						|
		// by scaling down, the readiness minimum would be crossed.)
 | 
						|
		noop bool
 | 
						|
	}
 | 
						|
 | 
						|
	tests := []struct {
 | 
						|
		name string
 | 
						|
		// oldRc is the "from" deployment
 | 
						|
		oldRc *corev1.ReplicationController
 | 
						|
		// newRc is the "to" deployment
 | 
						|
		newRc *corev1.ReplicationController
 | 
						|
		// whether newRc existed (false means it was created)
 | 
						|
		newRcExists bool
 | 
						|
		maxUnavail  intstr.IntOrString
 | 
						|
		maxSurge    intstr.IntOrString
 | 
						|
		// expected is the sequence of up/down events that will be simulated and
 | 
						|
		// verified
 | 
						|
		expected []interface{}
 | 
						|
		// output is the expected textual output written
 | 
						|
		output string
 | 
						|
	}{
 | 
						|
		{
 | 
						|
			name:        "10->10 30/0 fast readiness",
 | 
						|
			oldRc:       oldRc(10, 10),
 | 
						|
			newRc:       newRc(0, 10),
 | 
						|
			newRcExists: false,
 | 
						|
			maxUnavail:  intstr.FromString("30%"),
 | 
						|
			maxSurge:    intstr.FromString("0%"),
 | 
						|
			expected: []interface{}{
 | 
						|
				down{oldReady: 10, newReady: 0, to: 7},
 | 
						|
				up{3},
 | 
						|
				down{oldReady: 7, newReady: 3, to: 4},
 | 
						|
				up{6},
 | 
						|
				down{oldReady: 4, newReady: 6, to: 1},
 | 
						|
				up{9},
 | 
						|
				down{oldReady: 1, newReady: 9, to: 0},
 | 
						|
				up{10},
 | 
						|
			},
 | 
						|
			output: `Created foo-v2
 | 
						|
Scaling up foo-v2 from 0 to 10, scaling down foo-v1 from 10 to 0 (keep 7 pods available, don't exceed 10 pods)
 | 
						|
Scaling foo-v1 down to 7
 | 
						|
Scaling foo-v2 up to 3
 | 
						|
Scaling foo-v1 down to 4
 | 
						|
Scaling foo-v2 up to 6
 | 
						|
Scaling foo-v1 down to 1
 | 
						|
Scaling foo-v2 up to 9
 | 
						|
Scaling foo-v1 down to 0
 | 
						|
Scaling foo-v2 up to 10
 | 
						|
`,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:        "10->10 30/0 delayed readiness",
 | 
						|
			oldRc:       oldRc(10, 10),
 | 
						|
			newRc:       newRc(0, 10),
 | 
						|
			newRcExists: false,
 | 
						|
			maxUnavail:  intstr.FromString("30%"),
 | 
						|
			maxSurge:    intstr.FromString("0%"),
 | 
						|
			expected: []interface{}{
 | 
						|
				down{oldReady: 10, newReady: 0, to: 7},
 | 
						|
				up{3},
 | 
						|
				down{oldReady: 7, newReady: 0, noop: true},
 | 
						|
				down{oldReady: 7, newReady: 1, to: 6},
 | 
						|
				up{4},
 | 
						|
				down{oldReady: 6, newReady: 4, to: 3},
 | 
						|
				up{7},
 | 
						|
				down{oldReady: 3, newReady: 7, to: 0},
 | 
						|
				up{10},
 | 
						|
			},
 | 
						|
			output: `Created foo-v2
 | 
						|
Scaling up foo-v2 from 0 to 10, scaling down foo-v1 from 10 to 0 (keep 7 pods available, don't exceed 10 pods)
 | 
						|
Scaling foo-v1 down to 7
 | 
						|
Scaling foo-v2 up to 3
 | 
						|
Scaling foo-v1 down to 6
 | 
						|
Scaling foo-v2 up to 4
 | 
						|
Scaling foo-v1 down to 3
 | 
						|
Scaling foo-v2 up to 7
 | 
						|
Scaling foo-v1 down to 0
 | 
						|
Scaling foo-v2 up to 10
 | 
						|
`,
 | 
						|
		}, {
 | 
						|
			name:        "10->10 30/0 fast readiness, continuation",
 | 
						|
			oldRc:       oldRc(7, 10),
 | 
						|
			newRc:       newRc(3, 10),
 | 
						|
			newRcExists: false,
 | 
						|
			maxUnavail:  intstr.FromString("30%"),
 | 
						|
			maxSurge:    intstr.FromString("0%"),
 | 
						|
			expected: []interface{}{
 | 
						|
				down{oldReady: 7, newReady: 3, to: 4},
 | 
						|
				up{6},
 | 
						|
				down{oldReady: 4, newReady: 6, to: 1},
 | 
						|
				up{9},
 | 
						|
				down{oldReady: 1, newReady: 9, to: 0},
 | 
						|
				up{10},
 | 
						|
			},
 | 
						|
			output: `Created foo-v2
 | 
						|
Scaling up foo-v2 from 3 to 10, scaling down foo-v1 from 7 to 0 (keep 7 pods available, don't exceed 10 pods)
 | 
						|
Scaling foo-v1 down to 4
 | 
						|
Scaling foo-v2 up to 6
 | 
						|
Scaling foo-v1 down to 1
 | 
						|
Scaling foo-v2 up to 9
 | 
						|
Scaling foo-v1 down to 0
 | 
						|
Scaling foo-v2 up to 10
 | 
						|
`,
 | 
						|
		}, {
 | 
						|
			name:        "10->10 30/0 fast readiness, continued after restart which prevented first scale-up",
 | 
						|
			oldRc:       oldRc(7, 10),
 | 
						|
			newRc:       newRc(0, 10),
 | 
						|
			newRcExists: false,
 | 
						|
			maxUnavail:  intstr.FromString("30%"),
 | 
						|
			maxSurge:    intstr.FromString("0%"),
 | 
						|
			expected: []interface{}{
 | 
						|
				down{oldReady: 7, newReady: 0, noop: true},
 | 
						|
				up{3},
 | 
						|
				down{oldReady: 7, newReady: 3, to: 4},
 | 
						|
				up{6},
 | 
						|
				down{oldReady: 4, newReady: 6, to: 1},
 | 
						|
				up{9},
 | 
						|
				down{oldReady: 1, newReady: 9, to: 0},
 | 
						|
				up{10},
 | 
						|
			},
 | 
						|
			output: `Created foo-v2
 | 
						|
Scaling up foo-v2 from 0 to 10, scaling down foo-v1 from 7 to 0 (keep 7 pods available, don't exceed 10 pods)
 | 
						|
Scaling foo-v2 up to 3
 | 
						|
Scaling foo-v1 down to 4
 | 
						|
Scaling foo-v2 up to 6
 | 
						|
Scaling foo-v1 down to 1
 | 
						|
Scaling foo-v2 up to 9
 | 
						|
Scaling foo-v1 down to 0
 | 
						|
Scaling foo-v2 up to 10
 | 
						|
`,
 | 
						|
		}, {
 | 
						|
			name:        "10->10 0/30 fast readiness",
 | 
						|
			oldRc:       oldRc(10, 10),
 | 
						|
			newRc:       newRc(0, 10),
 | 
						|
			newRcExists: false,
 | 
						|
			maxUnavail:  intstr.FromString("0%"),
 | 
						|
			maxSurge:    intstr.FromString("30%"),
 | 
						|
			expected: []interface{}{
 | 
						|
				up{3},
 | 
						|
				down{oldReady: 10, newReady: 3, to: 7},
 | 
						|
				up{6},
 | 
						|
				down{oldReady: 7, newReady: 6, to: 4},
 | 
						|
				up{9},
 | 
						|
				down{oldReady: 4, newReady: 9, to: 1},
 | 
						|
				up{10},
 | 
						|
				down{oldReady: 1, newReady: 10, to: 0},
 | 
						|
			},
 | 
						|
			output: `Created foo-v2
 | 
						|
Scaling up foo-v2 from 0 to 10, scaling down foo-v1 from 10 to 0 (keep 10 pods available, don't exceed 13 pods)
 | 
						|
Scaling foo-v2 up to 3
 | 
						|
Scaling foo-v1 down to 7
 | 
						|
Scaling foo-v2 up to 6
 | 
						|
Scaling foo-v1 down to 4
 | 
						|
Scaling foo-v2 up to 9
 | 
						|
Scaling foo-v1 down to 1
 | 
						|
Scaling foo-v2 up to 10
 | 
						|
Scaling foo-v1 down to 0
 | 
						|
`,
 | 
						|
		}, {
 | 
						|
			name:        "10->10 0/30 delayed readiness",
 | 
						|
			oldRc:       oldRc(10, 10),
 | 
						|
			newRc:       newRc(0, 10),
 | 
						|
			newRcExists: false,
 | 
						|
			maxUnavail:  intstr.FromString("0%"),
 | 
						|
			maxSurge:    intstr.FromString("30%"),
 | 
						|
			expected: []interface{}{
 | 
						|
				up{3},
 | 
						|
				down{oldReady: 10, newReady: 0, noop: true},
 | 
						|
				down{oldReady: 10, newReady: 1, to: 9},
 | 
						|
				up{4},
 | 
						|
				down{oldReady: 9, newReady: 3, to: 7},
 | 
						|
				up{6},
 | 
						|
				down{oldReady: 7, newReady: 6, to: 4},
 | 
						|
				up{9},
 | 
						|
				down{oldReady: 4, newReady: 9, to: 1},
 | 
						|
				up{10},
 | 
						|
				down{oldReady: 1, newReady: 9, noop: true},
 | 
						|
				down{oldReady: 1, newReady: 10, to: 0},
 | 
						|
			},
 | 
						|
			output: `Created foo-v2
 | 
						|
Scaling up foo-v2 from 0 to 10, scaling down foo-v1 from 10 to 0 (keep 10 pods available, don't exceed 13 pods)
 | 
						|
Scaling foo-v2 up to 3
 | 
						|
Scaling foo-v1 down to 9
 | 
						|
Scaling foo-v2 up to 4
 | 
						|
Scaling foo-v1 down to 7
 | 
						|
Scaling foo-v2 up to 6
 | 
						|
Scaling foo-v1 down to 4
 | 
						|
Scaling foo-v2 up to 9
 | 
						|
Scaling foo-v1 down to 1
 | 
						|
Scaling foo-v2 up to 10
 | 
						|
Scaling foo-v1 down to 0
 | 
						|
`,
 | 
						|
		}, {
 | 
						|
			name:        "10->10 10/20 fast readiness",
 | 
						|
			oldRc:       oldRc(10, 10),
 | 
						|
			newRc:       newRc(0, 10),
 | 
						|
			newRcExists: false,
 | 
						|
			maxUnavail:  intstr.FromString("10%"),
 | 
						|
			maxSurge:    intstr.FromString("20%"),
 | 
						|
			expected: []interface{}{
 | 
						|
				up{2},
 | 
						|
				down{oldReady: 10, newReady: 2, to: 7},
 | 
						|
				up{5},
 | 
						|
				down{oldReady: 7, newReady: 5, to: 4},
 | 
						|
				up{8},
 | 
						|
				down{oldReady: 4, newReady: 8, to: 1},
 | 
						|
				up{10},
 | 
						|
				down{oldReady: 1, newReady: 10, to: 0},
 | 
						|
			},
 | 
						|
			output: `Created foo-v2
 | 
						|
Scaling up foo-v2 from 0 to 10, scaling down foo-v1 from 10 to 0 (keep 9 pods available, don't exceed 12 pods)
 | 
						|
Scaling foo-v2 up to 2
 | 
						|
Scaling foo-v1 down to 7
 | 
						|
Scaling foo-v2 up to 5
 | 
						|
Scaling foo-v1 down to 4
 | 
						|
Scaling foo-v2 up to 8
 | 
						|
Scaling foo-v1 down to 1
 | 
						|
Scaling foo-v2 up to 10
 | 
						|
Scaling foo-v1 down to 0
 | 
						|
`,
 | 
						|
		}, {
 | 
						|
			name:        "10->10 10/20 delayed readiness",
 | 
						|
			oldRc:       oldRc(10, 10),
 | 
						|
			newRc:       newRc(0, 10),
 | 
						|
			newRcExists: false,
 | 
						|
			maxUnavail:  intstr.FromString("10%"),
 | 
						|
			maxSurge:    intstr.FromString("20%"),
 | 
						|
			expected: []interface{}{
 | 
						|
				up{2},
 | 
						|
				down{oldReady: 10, newReady: 2, to: 7},
 | 
						|
				up{5},
 | 
						|
				down{oldReady: 7, newReady: 4, to: 5},
 | 
						|
				up{7},
 | 
						|
				down{oldReady: 5, newReady: 4, noop: true},
 | 
						|
				down{oldReady: 5, newReady: 7, to: 2},
 | 
						|
				up{10},
 | 
						|
				down{oldReady: 2, newReady: 9, to: 0},
 | 
						|
			},
 | 
						|
			output: `Created foo-v2
 | 
						|
Scaling up foo-v2 from 0 to 10, scaling down foo-v1 from 10 to 0 (keep 9 pods available, don't exceed 12 pods)
 | 
						|
Scaling foo-v2 up to 2
 | 
						|
Scaling foo-v1 down to 7
 | 
						|
Scaling foo-v2 up to 5
 | 
						|
Scaling foo-v1 down to 5
 | 
						|
Scaling foo-v2 up to 7
 | 
						|
Scaling foo-v1 down to 2
 | 
						|
Scaling foo-v2 up to 10
 | 
						|
Scaling foo-v1 down to 0
 | 
						|
`,
 | 
						|
		}, {
 | 
						|
			name:        "10->10 10/20 fast readiness continued after restart which prevented first scale-down",
 | 
						|
			oldRc:       oldRc(10, 10),
 | 
						|
			newRc:       newRc(2, 10),
 | 
						|
			newRcExists: false,
 | 
						|
			maxUnavail:  intstr.FromString("10%"),
 | 
						|
			maxSurge:    intstr.FromString("20%"),
 | 
						|
			expected: []interface{}{
 | 
						|
				down{oldReady: 10, newReady: 2, to: 7},
 | 
						|
				up{5},
 | 
						|
				down{oldReady: 7, newReady: 5, to: 4},
 | 
						|
				up{8},
 | 
						|
				down{oldReady: 4, newReady: 8, to: 1},
 | 
						|
				up{10},
 | 
						|
				down{oldReady: 1, newReady: 10, to: 0},
 | 
						|
			},
 | 
						|
			output: `Created foo-v2
 | 
						|
Scaling up foo-v2 from 2 to 10, scaling down foo-v1 from 10 to 0 (keep 9 pods available, don't exceed 12 pods)
 | 
						|
Scaling foo-v1 down to 7
 | 
						|
Scaling foo-v2 up to 5
 | 
						|
Scaling foo-v1 down to 4
 | 
						|
Scaling foo-v2 up to 8
 | 
						|
Scaling foo-v1 down to 1
 | 
						|
Scaling foo-v2 up to 10
 | 
						|
Scaling foo-v1 down to 0
 | 
						|
`,
 | 
						|
		}, {
 | 
						|
			name:        "10->10 0/100 fast readiness",
 | 
						|
			oldRc:       oldRc(10, 10),
 | 
						|
			newRc:       newRc(0, 10),
 | 
						|
			newRcExists: false,
 | 
						|
			maxUnavail:  intstr.FromString("0%"),
 | 
						|
			maxSurge:    intstr.FromString("100%"),
 | 
						|
			expected: []interface{}{
 | 
						|
				up{10},
 | 
						|
				down{oldReady: 10, newReady: 10, to: 0},
 | 
						|
			},
 | 
						|
			output: `Created foo-v2
 | 
						|
Scaling up foo-v2 from 0 to 10, scaling down foo-v1 from 10 to 0 (keep 10 pods available, don't exceed 20 pods)
 | 
						|
Scaling foo-v2 up to 10
 | 
						|
Scaling foo-v1 down to 0
 | 
						|
`,
 | 
						|
		}, {
 | 
						|
			name:        "10->10 0/100 delayed readiness",
 | 
						|
			oldRc:       oldRc(10, 10),
 | 
						|
			newRc:       newRc(0, 10),
 | 
						|
			newRcExists: false,
 | 
						|
			maxUnavail:  intstr.FromString("0%"),
 | 
						|
			maxSurge:    intstr.FromString("100%"),
 | 
						|
			expected: []interface{}{
 | 
						|
				up{10},
 | 
						|
				down{oldReady: 10, newReady: 0, noop: true},
 | 
						|
				down{oldReady: 10, newReady: 2, to: 8},
 | 
						|
				down{oldReady: 8, newReady: 7, to: 3},
 | 
						|
				down{oldReady: 3, newReady: 10, to: 0},
 | 
						|
			},
 | 
						|
			output: `Created foo-v2
 | 
						|
Scaling up foo-v2 from 0 to 10, scaling down foo-v1 from 10 to 0 (keep 10 pods available, don't exceed 20 pods)
 | 
						|
Scaling foo-v2 up to 10
 | 
						|
Scaling foo-v1 down to 8
 | 
						|
Scaling foo-v1 down to 3
 | 
						|
Scaling foo-v1 down to 0
 | 
						|
`,
 | 
						|
		}, {
 | 
						|
			name:        "10->10 100/0 fast readiness",
 | 
						|
			oldRc:       oldRc(10, 10),
 | 
						|
			newRc:       newRc(0, 10),
 | 
						|
			newRcExists: false,
 | 
						|
			maxUnavail:  intstr.FromString("100%"),
 | 
						|
			maxSurge:    intstr.FromString("0%"),
 | 
						|
			expected: []interface{}{
 | 
						|
				down{oldReady: 10, newReady: 0, to: 0},
 | 
						|
				up{10},
 | 
						|
			},
 | 
						|
			output: `Created foo-v2
 | 
						|
Scaling up foo-v2 from 0 to 10, scaling down foo-v1 from 10 to 0 (keep 0 pods available, don't exceed 10 pods)
 | 
						|
Scaling foo-v1 down to 0
 | 
						|
Scaling foo-v2 up to 10
 | 
						|
`,
 | 
						|
		}, {
 | 
						|
			name:        "1->1 25/25 maintain minimum availability",
 | 
						|
			oldRc:       oldRc(1, 1),
 | 
						|
			newRc:       newRc(0, 1),
 | 
						|
			newRcExists: false,
 | 
						|
			maxUnavail:  intstr.FromString("25%"),
 | 
						|
			maxSurge:    intstr.FromString("25%"),
 | 
						|
			expected: []interface{}{
 | 
						|
				up{1},
 | 
						|
				down{oldReady: 1, newReady: 0, noop: true},
 | 
						|
				down{oldReady: 1, newReady: 1, to: 0},
 | 
						|
			},
 | 
						|
			output: `Created foo-v2
 | 
						|
Scaling up foo-v2 from 0 to 1, scaling down foo-v1 from 1 to 0 (keep 1 pods available, don't exceed 2 pods)
 | 
						|
Scaling foo-v2 up to 1
 | 
						|
Scaling foo-v1 down to 0
 | 
						|
`,
 | 
						|
		}, {
 | 
						|
			name:        "1->1 0/10 delayed readiness",
 | 
						|
			oldRc:       oldRc(1, 1),
 | 
						|
			newRc:       newRc(0, 1),
 | 
						|
			newRcExists: false,
 | 
						|
			maxUnavail:  intstr.FromString("0%"),
 | 
						|
			maxSurge:    intstr.FromString("10%"),
 | 
						|
			expected: []interface{}{
 | 
						|
				up{1},
 | 
						|
				down{oldReady: 1, newReady: 0, noop: true},
 | 
						|
				down{oldReady: 1, newReady: 1, to: 0},
 | 
						|
			},
 | 
						|
			output: `Created foo-v2
 | 
						|
Scaling up foo-v2 from 0 to 1, scaling down foo-v1 from 1 to 0 (keep 1 pods available, don't exceed 2 pods)
 | 
						|
Scaling foo-v2 up to 1
 | 
						|
Scaling foo-v1 down to 0
 | 
						|
`,
 | 
						|
		}, {
 | 
						|
			name:        "1->1 10/10 delayed readiness",
 | 
						|
			oldRc:       oldRc(1, 1),
 | 
						|
			newRc:       newRc(0, 1),
 | 
						|
			newRcExists: false,
 | 
						|
			maxUnavail:  intstr.FromString("10%"),
 | 
						|
			maxSurge:    intstr.FromString("10%"),
 | 
						|
			expected: []interface{}{
 | 
						|
				up{1},
 | 
						|
				down{oldReady: 1, newReady: 0, noop: true},
 | 
						|
				down{oldReady: 1, newReady: 1, to: 0},
 | 
						|
			},
 | 
						|
			output: `Created foo-v2
 | 
						|
Scaling up foo-v2 from 0 to 1, scaling down foo-v1 from 1 to 0 (keep 1 pods available, don't exceed 2 pods)
 | 
						|
Scaling foo-v2 up to 1
 | 
						|
Scaling foo-v1 down to 0
 | 
						|
`,
 | 
						|
		}, {
 | 
						|
			name:        "3->3 1/1 fast readiness (absolute values)",
 | 
						|
			oldRc:       oldRc(3, 3),
 | 
						|
			newRc:       newRc(0, 3),
 | 
						|
			newRcExists: false,
 | 
						|
			maxUnavail:  intstr.FromInt(0),
 | 
						|
			maxSurge:    intstr.FromInt(1),
 | 
						|
			expected: []interface{}{
 | 
						|
				up{1},
 | 
						|
				down{oldReady: 3, newReady: 1, to: 2},
 | 
						|
				up{2},
 | 
						|
				down{oldReady: 2, newReady: 2, to: 1},
 | 
						|
				up{3},
 | 
						|
				down{oldReady: 1, newReady: 3, to: 0},
 | 
						|
			},
 | 
						|
			output: `Created foo-v2
 | 
						|
Scaling up foo-v2 from 0 to 3, scaling down foo-v1 from 3 to 0 (keep 3 pods available, don't exceed 4 pods)
 | 
						|
Scaling foo-v2 up to 1
 | 
						|
Scaling foo-v1 down to 2
 | 
						|
Scaling foo-v2 up to 2
 | 
						|
Scaling foo-v1 down to 1
 | 
						|
Scaling foo-v2 up to 3
 | 
						|
Scaling foo-v1 down to 0
 | 
						|
`,
 | 
						|
		}, {
 | 
						|
			name:        "10->10 0/20 fast readiness, continued after restart which resulted in partial first scale-up",
 | 
						|
			oldRc:       oldRc(6, 10),
 | 
						|
			newRc:       newRc(5, 10),
 | 
						|
			newRcExists: false,
 | 
						|
			maxUnavail:  intstr.FromString("0%"),
 | 
						|
			maxSurge:    intstr.FromString("20%"),
 | 
						|
			expected: []interface{}{
 | 
						|
				up{6},
 | 
						|
				down{oldReady: 6, newReady: 6, to: 4},
 | 
						|
				up{8},
 | 
						|
				down{oldReady: 4, newReady: 8, to: 2},
 | 
						|
				up{10},
 | 
						|
				down{oldReady: 1, newReady: 10, to: 0},
 | 
						|
			},
 | 
						|
			output: `Created foo-v2
 | 
						|
Scaling up foo-v2 from 5 to 10, scaling down foo-v1 from 6 to 0 (keep 10 pods available, don't exceed 12 pods)
 | 
						|
Scaling foo-v2 up to 6
 | 
						|
Scaling foo-v1 down to 4
 | 
						|
Scaling foo-v2 up to 8
 | 
						|
Scaling foo-v1 down to 2
 | 
						|
Scaling foo-v2 up to 10
 | 
						|
Scaling foo-v1 down to 0
 | 
						|
`,
 | 
						|
		}, {
 | 
						|
			name:        "10->20 0/300 fast readiness",
 | 
						|
			oldRc:       oldRc(10, 10),
 | 
						|
			newRc:       newRc(0, 20),
 | 
						|
			newRcExists: false,
 | 
						|
			maxUnavail:  intstr.FromString("0%"),
 | 
						|
			maxSurge:    intstr.FromString("300%"),
 | 
						|
			expected: []interface{}{
 | 
						|
				up{20},
 | 
						|
				down{oldReady: 10, newReady: 20, to: 0},
 | 
						|
			},
 | 
						|
			output: `Created foo-v2
 | 
						|
Scaling up foo-v2 from 0 to 20, scaling down foo-v1 from 10 to 0 (keep 20 pods available, don't exceed 80 pods)
 | 
						|
Scaling foo-v2 up to 20
 | 
						|
Scaling foo-v1 down to 0
 | 
						|
`,
 | 
						|
		}, {
 | 
						|
			name:        "1->1 0/1 scale down unavailable rc to a ready rc (rollback)",
 | 
						|
			oldRc:       oldRc(1, 1),
 | 
						|
			newRc:       newRc(1, 1),
 | 
						|
			newRcExists: true,
 | 
						|
			maxUnavail:  intstr.FromInt(0),
 | 
						|
			maxSurge:    intstr.FromInt(1),
 | 
						|
			expected: []interface{}{
 | 
						|
				up{1},
 | 
						|
				down{oldReady: 0, newReady: 1, to: 0},
 | 
						|
			},
 | 
						|
			output: `Continuing update with existing controller foo-v2.
 | 
						|
Scaling up foo-v2 from 1 to 1, scaling down foo-v1 from 1 to 0 (keep 1 pods available, don't exceed 2 pods)
 | 
						|
Scaling foo-v1 down to 0
 | 
						|
`,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:        "3->0 1/1 desired 0 (absolute values)",
 | 
						|
			oldRc:       oldRc(3, 3),
 | 
						|
			newRc:       newRc(0, 0),
 | 
						|
			newRcExists: true,
 | 
						|
			maxUnavail:  intstr.FromInt(1),
 | 
						|
			maxSurge:    intstr.FromInt(1),
 | 
						|
			expected: []interface{}{
 | 
						|
				down{oldReady: 3, newReady: 0, to: 0},
 | 
						|
			},
 | 
						|
			output: `Continuing update with existing controller foo-v2.
 | 
						|
Scaling up foo-v2 from 0 to 0, scaling down foo-v1 from 3 to 0 (keep 0 pods available, don't exceed 1 pods)
 | 
						|
Scaling foo-v1 down to 0
 | 
						|
`,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:        "3->0 10/10 desired 0 (percentages)",
 | 
						|
			oldRc:       oldRc(3, 3),
 | 
						|
			newRc:       newRc(0, 0),
 | 
						|
			newRcExists: true,
 | 
						|
			maxUnavail:  intstr.FromString("10%"),
 | 
						|
			maxSurge:    intstr.FromString("10%"),
 | 
						|
			expected: []interface{}{
 | 
						|
				down{oldReady: 3, newReady: 0, to: 0},
 | 
						|
			},
 | 
						|
			output: `Continuing update with existing controller foo-v2.
 | 
						|
Scaling up foo-v2 from 0 to 0, scaling down foo-v1 from 3 to 0 (keep 0 pods available, don't exceed 0 pods)
 | 
						|
Scaling foo-v1 down to 0
 | 
						|
`,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:        "3->0 10/10 desired 0 (create new RC)",
 | 
						|
			oldRc:       oldRc(3, 3),
 | 
						|
			newRc:       newRc(0, 0),
 | 
						|
			newRcExists: false,
 | 
						|
			maxUnavail:  intstr.FromString("10%"),
 | 
						|
			maxSurge:    intstr.FromString("10%"),
 | 
						|
			expected: []interface{}{
 | 
						|
				down{oldReady: 3, newReady: 0, to: 0},
 | 
						|
			},
 | 
						|
			output: `Created foo-v2
 | 
						|
Scaling up foo-v2 from 0 to 0, scaling down foo-v1 from 3 to 0 (keep 0 pods available, don't exceed 0 pods)
 | 
						|
Scaling foo-v1 down to 0
 | 
						|
`,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:        "0->0 1/1 desired 0 (absolute values)",
 | 
						|
			oldRc:       oldRc(0, 0),
 | 
						|
			newRc:       newRc(0, 0),
 | 
						|
			newRcExists: true,
 | 
						|
			maxUnavail:  intstr.FromInt(1),
 | 
						|
			maxSurge:    intstr.FromInt(1),
 | 
						|
			expected: []interface{}{
 | 
						|
				down{oldReady: 0, newReady: 0, to: 0},
 | 
						|
			},
 | 
						|
			output: `Continuing update with existing controller foo-v2.
 | 
						|
Scaling up foo-v2 from 0 to 0, scaling down foo-v1 from 0 to 0 (keep 0 pods available, don't exceed 1 pods)
 | 
						|
`,
 | 
						|
		}, {
 | 
						|
			name:        "30->2 50%/0",
 | 
						|
			oldRc:       oldRc(30, 30),
 | 
						|
			newRc:       newRc(0, 2),
 | 
						|
			newRcExists: false,
 | 
						|
			maxUnavail:  intstr.FromString("50%"),
 | 
						|
			maxSurge:    intstr.FromInt(0),
 | 
						|
			expected: []interface{}{
 | 
						|
				down{oldReady: 30, newReady: 0, to: 1},
 | 
						|
				up{1},
 | 
						|
				down{oldReady: 1, newReady: 2, to: 0},
 | 
						|
				up{2},
 | 
						|
			},
 | 
						|
			output: `Created foo-v2
 | 
						|
Scaling up foo-v2 from 0 to 2, scaling down foo-v1 from 30 to 0 (keep 1 pods available, don't exceed 2 pods)
 | 
						|
Scaling foo-v1 down to 1
 | 
						|
Scaling foo-v2 up to 1
 | 
						|
Scaling foo-v1 down to 0
 | 
						|
Scaling foo-v2 up to 2
 | 
						|
`,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:        "2->2 1/0 blocked oldRc",
 | 
						|
			oldRc:       oldRc(2, 2),
 | 
						|
			newRc:       newRc(0, 2),
 | 
						|
			newRcExists: false,
 | 
						|
			maxUnavail:  intstr.FromInt(1),
 | 
						|
			maxSurge:    intstr.FromInt(0),
 | 
						|
			expected: []interface{}{
 | 
						|
				down{oldReady: 1, newReady: 0, to: 1},
 | 
						|
				up{1},
 | 
						|
				down{oldReady: 1, newReady: 1, to: 0},
 | 
						|
				up{2},
 | 
						|
			},
 | 
						|
			output: `Created foo-v2
 | 
						|
Scaling up foo-v2 from 0 to 2, scaling down foo-v1 from 2 to 0 (keep 1 pods available, don't exceed 2 pods)
 | 
						|
Scaling foo-v1 down to 1
 | 
						|
Scaling foo-v2 up to 1
 | 
						|
Scaling foo-v1 down to 0
 | 
						|
Scaling foo-v2 up to 2
 | 
						|
`,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:        "1->1 1/0 allow maxUnavailability",
 | 
						|
			oldRc:       oldRc(1, 1),
 | 
						|
			newRc:       newRc(0, 1),
 | 
						|
			newRcExists: false,
 | 
						|
			maxUnavail:  intstr.FromString("1%"),
 | 
						|
			maxSurge:    intstr.FromInt(0),
 | 
						|
			expected: []interface{}{
 | 
						|
				down{oldReady: 1, newReady: 0, to: 0},
 | 
						|
				up{1},
 | 
						|
			},
 | 
						|
			output: `Created foo-v2
 | 
						|
Scaling up foo-v2 from 0 to 1, scaling down foo-v1 from 1 to 0 (keep 0 pods available, don't exceed 1 pods)
 | 
						|
Scaling foo-v1 down to 0
 | 
						|
Scaling foo-v2 up to 1
 | 
						|
`,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:        "1->2 25/25 complex asymmetric deployment",
 | 
						|
			oldRc:       oldRc(1, 1),
 | 
						|
			newRc:       newRc(0, 2),
 | 
						|
			newRcExists: false,
 | 
						|
			maxUnavail:  intstr.FromString("25%"),
 | 
						|
			maxSurge:    intstr.FromString("25%"),
 | 
						|
			expected: []interface{}{
 | 
						|
				up{2},
 | 
						|
				down{oldReady: 1, newReady: 2, to: 0},
 | 
						|
			},
 | 
						|
			output: `Created foo-v2
 | 
						|
Scaling up foo-v2 from 0 to 2, scaling down foo-v1 from 1 to 0 (keep 2 pods available, don't exceed 3 pods)
 | 
						|
Scaling foo-v2 up to 2
 | 
						|
Scaling foo-v1 down to 0
 | 
						|
`,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:        "2->2 25/1 maxSurge trumps maxUnavailable",
 | 
						|
			oldRc:       oldRc(2, 2),
 | 
						|
			newRc:       newRc(0, 2),
 | 
						|
			newRcExists: false,
 | 
						|
			maxUnavail:  intstr.FromString("25%"),
 | 
						|
			maxSurge:    intstr.FromString("1%"),
 | 
						|
			expected: []interface{}{
 | 
						|
				up{1},
 | 
						|
				down{oldReady: 2, newReady: 1, to: 1},
 | 
						|
				up{2},
 | 
						|
				down{oldReady: 1, newReady: 2, to: 0},
 | 
						|
			},
 | 
						|
			output: `Created foo-v2
 | 
						|
Scaling up foo-v2 from 0 to 2, scaling down foo-v1 from 2 to 0 (keep 2 pods available, don't exceed 3 pods)
 | 
						|
Scaling foo-v2 up to 1
 | 
						|
Scaling foo-v1 down to 1
 | 
						|
Scaling foo-v2 up to 2
 | 
						|
Scaling foo-v1 down to 0
 | 
						|
`,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:        "2->2 25/0 maxUnavailable resolves to zero, then one",
 | 
						|
			oldRc:       oldRc(2, 2),
 | 
						|
			newRc:       newRc(0, 2),
 | 
						|
			newRcExists: false,
 | 
						|
			maxUnavail:  intstr.FromString("25%"),
 | 
						|
			maxSurge:    intstr.FromString("0%"),
 | 
						|
			expected: []interface{}{
 | 
						|
				down{oldReady: 2, newReady: 0, to: 1},
 | 
						|
				up{1},
 | 
						|
				down{oldReady: 1, newReady: 1, to: 0},
 | 
						|
				up{2},
 | 
						|
			},
 | 
						|
			output: `Created foo-v2
 | 
						|
Scaling up foo-v2 from 0 to 2, scaling down foo-v1 from 2 to 0 (keep 1 pods available, don't exceed 2 pods)
 | 
						|
Scaling foo-v1 down to 1
 | 
						|
Scaling foo-v2 up to 1
 | 
						|
Scaling foo-v1 down to 0
 | 
						|
Scaling foo-v2 up to 2
 | 
						|
`,
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	for i, tt := range tests {
 | 
						|
		// Extract expectations into some makeshift FIFOs so they can be returned
 | 
						|
		// in the correct order from the right places. This lets scale downs be
 | 
						|
		// expressed a single event even though the data is used from multiple
 | 
						|
		// interface calls.
 | 
						|
		t.Run(tt.name, func(t *testing.T) {
 | 
						|
			oldReady := []int{}
 | 
						|
			newReady := []int{}
 | 
						|
			upTo := []int{}
 | 
						|
			downTo := []int{}
 | 
						|
			for _, event := range tt.expected {
 | 
						|
				switch e := event.(type) {
 | 
						|
				case down:
 | 
						|
					oldReady = append(oldReady, e.oldReady)
 | 
						|
					newReady = append(newReady, e.newReady)
 | 
						|
					if !e.noop {
 | 
						|
						downTo = append(downTo, e.to)
 | 
						|
					}
 | 
						|
				case up:
 | 
						|
					upTo = append(upTo, e.to)
 | 
						|
				}
 | 
						|
			}
 | 
						|
 | 
						|
			// Make a way to get the next item from our FIFOs. Returns -1 if the array
 | 
						|
			// is empty.
 | 
						|
			next := func(s *[]int) int {
 | 
						|
				slice := *s
 | 
						|
				v := -1
 | 
						|
				if len(slice) > 0 {
 | 
						|
					v = slice[0]
 | 
						|
					if len(slice) > 1 {
 | 
						|
						*s = slice[1:]
 | 
						|
					} else {
 | 
						|
						*s = []int{}
 | 
						|
					}
 | 
						|
				}
 | 
						|
				return v
 | 
						|
			}
 | 
						|
			t.Logf("running test %d (%s) (up: %v, down: %v, oldReady: %v, newReady: %v)", i, tt.name, upTo, downTo, oldReady, newReady)
 | 
						|
			updater := &RollingUpdater{
 | 
						|
				ns: "default",
 | 
						|
				scaleAndWait: func(rc *corev1.ReplicationController, retry *RetryParams, wait *RetryParams) (*corev1.ReplicationController, error) {
 | 
						|
					// Return a scale up or scale down expectation depending on the rc,
 | 
						|
					// and throw errors if there is no expectation expressed for this
 | 
						|
					// call.
 | 
						|
					expected := -1
 | 
						|
					switch {
 | 
						|
					case rc == tt.newRc:
 | 
						|
						t.Logf("scaling up %s to %d", rc.Name, rc.Spec.Replicas)
 | 
						|
						expected = next(&upTo)
 | 
						|
					case rc == tt.oldRc:
 | 
						|
						t.Logf("scaling down %s to %d", rc.Name, rc.Spec.Replicas)
 | 
						|
						expected = next(&downTo)
 | 
						|
					}
 | 
						|
					if expected == -1 {
 | 
						|
						t.Fatalf("unexpected scale of %s to %d", rc.Name, rc.Spec.Replicas)
 | 
						|
					} else if e, a := expected, int(*rc.Spec.Replicas); e != a {
 | 
						|
						t.Fatalf("expected scale of %s to %d, got %d", rc.Name, e, a)
 | 
						|
					}
 | 
						|
					// Simulate the scale.
 | 
						|
					rc.Status.Replicas = *rc.Spec.Replicas
 | 
						|
					return rc, nil
 | 
						|
				},
 | 
						|
				getOrCreateTargetController: func(controller *corev1.ReplicationController, sourceID string) (*corev1.ReplicationController, bool, error) {
 | 
						|
					// Simulate a create vs. update of an existing controller.
 | 
						|
					return tt.newRc, tt.newRcExists, nil
 | 
						|
				},
 | 
						|
				cleanup: func(oldRc, newRc *corev1.ReplicationController, config *RollingUpdaterConfig) error {
 | 
						|
					return nil
 | 
						|
				},
 | 
						|
			}
 | 
						|
			// Set up a mock readiness check which handles the test assertions.
 | 
						|
			updater.getReadyPods = func(oldRc, newRc *corev1.ReplicationController, minReadySecondsDeadline int32) (int32, int32, error) {
 | 
						|
				// Return simulated readiness, and throw an error if this call has no
 | 
						|
				// expectations defined.
 | 
						|
				oldReady := next(&oldReady)
 | 
						|
				newReady := next(&newReady)
 | 
						|
				if oldReady == -1 || newReady == -1 {
 | 
						|
					t.Fatalf("unexpected getReadyPods call for:\noldRc: %#v\nnewRc: %#v", oldRc, newRc)
 | 
						|
				}
 | 
						|
				return int32(oldReady), int32(newReady), nil
 | 
						|
			}
 | 
						|
			var buffer bytes.Buffer
 | 
						|
			config := &RollingUpdaterConfig{
 | 
						|
				Out:            &buffer,
 | 
						|
				OldRc:          tt.oldRc,
 | 
						|
				NewRc:          tt.newRc,
 | 
						|
				UpdatePeriod:   0,
 | 
						|
				Interval:       time.Millisecond,
 | 
						|
				Timeout:        time.Millisecond,
 | 
						|
				CleanupPolicy:  DeleteRollingUpdateCleanupPolicy,
 | 
						|
				MaxUnavailable: tt.maxUnavail,
 | 
						|
				MaxSurge:       tt.maxSurge,
 | 
						|
			}
 | 
						|
			err := updater.Update(config)
 | 
						|
			if err != nil {
 | 
						|
				t.Errorf("unexpected error: %v", err)
 | 
						|
			}
 | 
						|
			if buffer.String() != tt.output {
 | 
						|
				t.Errorf("Bad output. expected:\n%s\ngot:\n%s", tt.output, buffer.String())
 | 
						|
			}
 | 
						|
		})
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// TestUpdate_progressTimeout ensures that an update which isn't making any
 | 
						|
// progress will eventually time out with a specified error.
 | 
						|
func TestUpdate_progressTimeout(t *testing.T) {
 | 
						|
	oldRc := oldRc(2, 2)
 | 
						|
	newRc := newRc(0, 2)
 | 
						|
	updater := &RollingUpdater{
 | 
						|
		ns: "default",
 | 
						|
		scaleAndWait: func(rc *corev1.ReplicationController, retry *RetryParams, wait *RetryParams) (*corev1.ReplicationController, error) {
 | 
						|
			// Do nothing.
 | 
						|
			return rc, nil
 | 
						|
		},
 | 
						|
		getOrCreateTargetController: func(controller *corev1.ReplicationController, sourceID string) (*corev1.ReplicationController, bool, error) {
 | 
						|
			return newRc, false, nil
 | 
						|
		},
 | 
						|
		cleanup: func(oldRc, newRc *corev1.ReplicationController, config *RollingUpdaterConfig) error {
 | 
						|
			return nil
 | 
						|
		},
 | 
						|
	}
 | 
						|
	updater.getReadyPods = func(oldRc, newRc *corev1.ReplicationController, minReadySeconds int32) (int32, int32, error) {
 | 
						|
		// Coerce a timeout by pods never becoming ready.
 | 
						|
		return 0, 0, nil
 | 
						|
	}
 | 
						|
	var buffer bytes.Buffer
 | 
						|
	config := &RollingUpdaterConfig{
 | 
						|
		Out:            &buffer,
 | 
						|
		OldRc:          oldRc,
 | 
						|
		NewRc:          newRc,
 | 
						|
		UpdatePeriod:   0,
 | 
						|
		Interval:       time.Millisecond,
 | 
						|
		Timeout:        time.Millisecond,
 | 
						|
		CleanupPolicy:  DeleteRollingUpdateCleanupPolicy,
 | 
						|
		MaxUnavailable: intstr.FromInt(0),
 | 
						|
		MaxSurge:       intstr.FromInt(1),
 | 
						|
	}
 | 
						|
	err := updater.Update(config)
 | 
						|
	if err == nil {
 | 
						|
		t.Fatalf("expected an error")
 | 
						|
	}
 | 
						|
	if e, a := "timed out waiting for any update progress to be made", err.Error(); e != a {
 | 
						|
		t.Fatalf("expected error message: %s, got: %s", e, a)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func TestUpdate_assignOriginalAnnotation(t *testing.T) {
 | 
						|
	oldRc := oldRc(1, 1)
 | 
						|
	delete(oldRc.Annotations, originalReplicasAnnotation)
 | 
						|
	newRc := newRc(1, 1)
 | 
						|
	fake := fake.NewSimpleClientset(oldRc)
 | 
						|
	updater := &RollingUpdater{
 | 
						|
		rcClient:  fake.CoreV1(),
 | 
						|
		podClient: fake.CoreV1(),
 | 
						|
		ns:        "default",
 | 
						|
		scaleAndWait: func(rc *corev1.ReplicationController, retry *RetryParams, wait *RetryParams) (*corev1.ReplicationController, error) {
 | 
						|
			return rc, nil
 | 
						|
		},
 | 
						|
		getOrCreateTargetController: func(controller *corev1.ReplicationController, sourceID string) (*corev1.ReplicationController, bool, error) {
 | 
						|
			return newRc, false, nil
 | 
						|
		},
 | 
						|
		cleanup: func(oldRc, newRc *corev1.ReplicationController, config *RollingUpdaterConfig) error {
 | 
						|
			return nil
 | 
						|
		},
 | 
						|
		getReadyPods: func(oldRc, newRc *corev1.ReplicationController, minReadySeconds int32) (int32, int32, error) {
 | 
						|
			return 1, 1, nil
 | 
						|
		},
 | 
						|
	}
 | 
						|
	var buffer bytes.Buffer
 | 
						|
	config := &RollingUpdaterConfig{
 | 
						|
		Out:            &buffer,
 | 
						|
		OldRc:          oldRc,
 | 
						|
		NewRc:          newRc,
 | 
						|
		UpdatePeriod:   0,
 | 
						|
		Interval:       time.Millisecond,
 | 
						|
		Timeout:        time.Millisecond,
 | 
						|
		CleanupPolicy:  DeleteRollingUpdateCleanupPolicy,
 | 
						|
		MaxUnavailable: intstr.FromString("100%"),
 | 
						|
	}
 | 
						|
	err := updater.Update(config)
 | 
						|
	if err != nil {
 | 
						|
		t.Fatalf("unexpected error: %v", err)
 | 
						|
	}
 | 
						|
	updateAction := fake.Actions()[1].(testcore.UpdateAction)
 | 
						|
	if updateAction.GetResource().GroupResource() != corev1.Resource("replicationcontrollers") {
 | 
						|
		t.Fatalf("expected rc to be updated: %#v", updateAction)
 | 
						|
	}
 | 
						|
	if e, a := "1", updateAction.GetObject().(*corev1.ReplicationController).Annotations[originalReplicasAnnotation]; e != a {
 | 
						|
		t.Fatalf("expected annotation value %s, got %s", e, a)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func TestRollingUpdater_multipleContainersInPod(t *testing.T) {
 | 
						|
	tests := []struct {
 | 
						|
		name          string
 | 
						|
		oldRc         *corev1.ReplicationController
 | 
						|
		newRc         *corev1.ReplicationController
 | 
						|
		container     string
 | 
						|
		image         string
 | 
						|
		deploymentKey string
 | 
						|
	}{
 | 
						|
		{
 | 
						|
			name: "test1",
 | 
						|
			oldRc: &corev1.ReplicationController{
 | 
						|
				ObjectMeta: metav1.ObjectMeta{
 | 
						|
					Namespace: metav1.NamespaceDefault,
 | 
						|
					Name:      "foo",
 | 
						|
				},
 | 
						|
				Spec: corev1.ReplicationControllerSpec{
 | 
						|
					Selector: map[string]string{
 | 
						|
						"dk": "old",
 | 
						|
					},
 | 
						|
					Template: &corev1.PodTemplateSpec{
 | 
						|
						ObjectMeta: metav1.ObjectMeta{
 | 
						|
							Labels: map[string]string{
 | 
						|
								"dk": "old",
 | 
						|
							},
 | 
						|
						},
 | 
						|
						Spec: corev1.PodSpec{
 | 
						|
							Containers: []corev1.Container{
 | 
						|
								{
 | 
						|
									Name:  "container1",
 | 
						|
									Image: "image1",
 | 
						|
								},
 | 
						|
								{
 | 
						|
									Name:  "container2",
 | 
						|
									Image: "image2",
 | 
						|
								},
 | 
						|
							},
 | 
						|
						},
 | 
						|
					},
 | 
						|
				},
 | 
						|
			},
 | 
						|
			newRc: &corev1.ReplicationController{
 | 
						|
				ObjectMeta: metav1.ObjectMeta{
 | 
						|
					Namespace: metav1.NamespaceDefault,
 | 
						|
					Name:      "foo",
 | 
						|
				},
 | 
						|
				Spec: corev1.ReplicationControllerSpec{
 | 
						|
					Selector: map[string]string{
 | 
						|
						"dk": "old",
 | 
						|
					},
 | 
						|
					Template: &corev1.PodTemplateSpec{
 | 
						|
						ObjectMeta: metav1.ObjectMeta{
 | 
						|
							Labels: map[string]string{
 | 
						|
								"dk": "old",
 | 
						|
							},
 | 
						|
						},
 | 
						|
						Spec: corev1.PodSpec{
 | 
						|
							Containers: []corev1.Container{
 | 
						|
								{
 | 
						|
									Name:  "container1",
 | 
						|
									Image: "newimage",
 | 
						|
								},
 | 
						|
								{
 | 
						|
									Name:  "container2",
 | 
						|
									Image: "image2",
 | 
						|
								},
 | 
						|
							},
 | 
						|
						},
 | 
						|
					},
 | 
						|
				},
 | 
						|
			},
 | 
						|
			container:     "container1",
 | 
						|
			image:         "newimage",
 | 
						|
			deploymentKey: "dk",
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "test2",
 | 
						|
			oldRc: &corev1.ReplicationController{
 | 
						|
				ObjectMeta: metav1.ObjectMeta{
 | 
						|
					Namespace: metav1.NamespaceDefault,
 | 
						|
					Name:      "bar",
 | 
						|
				},
 | 
						|
				Spec: corev1.ReplicationControllerSpec{
 | 
						|
					Selector: map[string]string{
 | 
						|
						"dk": "old",
 | 
						|
					},
 | 
						|
					Template: &corev1.PodTemplateSpec{
 | 
						|
						ObjectMeta: metav1.ObjectMeta{
 | 
						|
							Labels: map[string]string{
 | 
						|
								"dk": "old",
 | 
						|
							},
 | 
						|
						},
 | 
						|
						Spec: corev1.PodSpec{
 | 
						|
							Containers: []corev1.Container{
 | 
						|
								{
 | 
						|
									Name:  "container1",
 | 
						|
									Image: "image1",
 | 
						|
								},
 | 
						|
							},
 | 
						|
						},
 | 
						|
					},
 | 
						|
				},
 | 
						|
			},
 | 
						|
			newRc: &corev1.ReplicationController{
 | 
						|
				ObjectMeta: metav1.ObjectMeta{
 | 
						|
					Namespace: metav1.NamespaceDefault,
 | 
						|
					Name:      "bar",
 | 
						|
				},
 | 
						|
				Spec: corev1.ReplicationControllerSpec{
 | 
						|
					Selector: map[string]string{
 | 
						|
						"dk": "old",
 | 
						|
					},
 | 
						|
					Template: &corev1.PodTemplateSpec{
 | 
						|
						ObjectMeta: metav1.ObjectMeta{
 | 
						|
							Labels: map[string]string{
 | 
						|
								"dk": "old",
 | 
						|
							},
 | 
						|
						},
 | 
						|
						Spec: corev1.PodSpec{
 | 
						|
							Containers: []corev1.Container{
 | 
						|
								{
 | 
						|
									Name:  "container1",
 | 
						|
									Image: "newimage",
 | 
						|
								},
 | 
						|
							},
 | 
						|
						},
 | 
						|
					},
 | 
						|
				},
 | 
						|
			},
 | 
						|
			container:     "container1",
 | 
						|
			image:         "newimage",
 | 
						|
			deploymentKey: "dk",
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	for _, tt := range tests {
 | 
						|
		t.Run(tt.name, func(t *testing.T) {
 | 
						|
			fake := fake.NewSimpleClientset(tt.oldRc)
 | 
						|
 | 
						|
			codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
 | 
						|
 | 
						|
			deploymentHash, err := util.HashObject(tt.newRc, codec)
 | 
						|
			if err != nil {
 | 
						|
				t.Errorf("unexpected error: %v", err)
 | 
						|
			}
 | 
						|
 | 
						|
			tt.newRc.Spec.Selector[tt.deploymentKey] = deploymentHash
 | 
						|
			tt.newRc.Spec.Template.Labels[tt.deploymentKey] = deploymentHash
 | 
						|
			tt.newRc.Name = fmt.Sprintf("%s-%s", tt.newRc.Name, deploymentHash)
 | 
						|
 | 
						|
			config := &NewControllerConfig{
 | 
						|
				Namespace:     metav1.NamespaceDefault,
 | 
						|
				OldName:       tt.oldRc.ObjectMeta.Name,
 | 
						|
				NewName:       tt.newRc.ObjectMeta.Name,
 | 
						|
				Image:         tt.image,
 | 
						|
				Container:     tt.container,
 | 
						|
				DeploymentKey: tt.deploymentKey,
 | 
						|
			}
 | 
						|
			updatedRc, err := CreateNewControllerFromCurrentController(fake.CoreV1(), codec, config)
 | 
						|
			if err != nil {
 | 
						|
				t.Errorf("unexpected error: %v", err)
 | 
						|
			}
 | 
						|
			if !reflect.DeepEqual(updatedRc, tt.newRc) {
 | 
						|
				t.Errorf("expected:\n%#v\ngot:\n%#v\n", tt.newRc, updatedRc)
 | 
						|
			}
 | 
						|
		})
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// TestRollingUpdater_cleanupWithClients ensures that the cleanup policy is
 | 
						|
// correctly implemented.
 | 
						|
func TestRollingUpdater_cleanupWithClients(t *testing.T) {
 | 
						|
	rc := oldRc(2, 2)
 | 
						|
	rcExisting := newRc(1, 3)
 | 
						|
 | 
						|
	tests := []struct {
 | 
						|
		name      string
 | 
						|
		policy    RollingUpdaterCleanupPolicy
 | 
						|
		responses []runtime.Object
 | 
						|
		expected  []string
 | 
						|
	}{
 | 
						|
		{
 | 
						|
			name:      "preserve",
 | 
						|
			policy:    PreserveRollingUpdateCleanupPolicy,
 | 
						|
			responses: []runtime.Object{rcExisting},
 | 
						|
			expected: []string{
 | 
						|
				"get",
 | 
						|
				"update",
 | 
						|
				"get",
 | 
						|
				"get",
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:      "delete",
 | 
						|
			policy:    DeleteRollingUpdateCleanupPolicy,
 | 
						|
			responses: []runtime.Object{rcExisting},
 | 
						|
			expected: []string{
 | 
						|
				"get",
 | 
						|
				"update",
 | 
						|
				"get",
 | 
						|
				"get",
 | 
						|
				"delete",
 | 
						|
			},
 | 
						|
		},
 | 
						|
		//{
 | 
						|
		// This cases is separated to a standalone
 | 
						|
		// TestRollingUpdater_cleanupWithClients_Rename. We have to do this
 | 
						|
		// because the unversioned fake client is unable to delete objects.
 | 
						|
		// TODO: uncomment this case when the unversioned fake client uses
 | 
						|
		// pkg/client/testing/core.
 | 
						|
		//	{
 | 
						|
		//		name:      "rename",
 | 
						|
		//		policy:    RenameRollingUpdateCleanupPolicy,
 | 
						|
		//		responses: []runtime.Object{rcExisting},
 | 
						|
		//		expected: []string{
 | 
						|
		//			"get",
 | 
						|
		//			"update",
 | 
						|
		//			"get",
 | 
						|
		//			"get",
 | 
						|
		//			"delete",
 | 
						|
		//			"create",
 | 
						|
		//			"delete",
 | 
						|
		//		},
 | 
						|
		//	},
 | 
						|
		//},
 | 
						|
	}
 | 
						|
 | 
						|
	for _, tt := range tests {
 | 
						|
		t.Run(tt.name, func(t *testing.T) {
 | 
						|
			objs := []runtime.Object{rc}
 | 
						|
			objs = append(objs, tt.responses...)
 | 
						|
			fake := fake.NewSimpleClientset(objs...)
 | 
						|
			updater := &RollingUpdater{
 | 
						|
				ns:        "default",
 | 
						|
				rcClient:  fake.CoreV1(),
 | 
						|
				podClient: fake.CoreV1(),
 | 
						|
			}
 | 
						|
			config := &RollingUpdaterConfig{
 | 
						|
				Out:           ioutil.Discard,
 | 
						|
				OldRc:         rc,
 | 
						|
				NewRc:         rcExisting,
 | 
						|
				UpdatePeriod:  0,
 | 
						|
				Interval:      time.Millisecond,
 | 
						|
				Timeout:       time.Millisecond,
 | 
						|
				CleanupPolicy: tt.policy,
 | 
						|
			}
 | 
						|
			err := updater.cleanupWithClients(rc, rcExisting, config)
 | 
						|
			if err != nil {
 | 
						|
				t.Errorf("unexpected error: %v", err)
 | 
						|
			}
 | 
						|
			if len(fake.Actions()) != len(tt.expected) {
 | 
						|
				t.Fatalf("%s: unexpected actions: %v, expected %v", tt.name, fake.Actions(), tt.expected)
 | 
						|
			}
 | 
						|
			for j, action := range fake.Actions() {
 | 
						|
				if e, a := tt.expected[j], action.GetVerb(); e != a {
 | 
						|
					t.Errorf("%s: unexpected action: expected %s, got %s", tt.name, e, a)
 | 
						|
				}
 | 
						|
			}
 | 
						|
		})
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// TestRollingUpdater_cleanupWithClients_Rename tests the rename cleanup policy. It's separated to
 | 
						|
// a standalone test because the unversioned fake client is unable to delete
 | 
						|
// objects.
 | 
						|
// TODO: move this test back to TestRollingUpdater_cleanupWithClients
 | 
						|
// when the fake client uses pkg/client/testing/core in the future.
 | 
						|
func TestRollingUpdater_cleanupWithClients_Rename(t *testing.T) {
 | 
						|
	rc := oldRc(2, 2)
 | 
						|
	rcExisting := newRc(1, 3)
 | 
						|
	expectedActions := []string{"delete", "get", "create"}
 | 
						|
	fake := fake.NewSimpleClientset()
 | 
						|
	fake.AddReactor("*", "*", func(action testcore.Action) (handled bool, ret runtime.Object, err error) {
 | 
						|
		switch action.(type) {
 | 
						|
		case testcore.CreateAction:
 | 
						|
			return true, nil, nil
 | 
						|
		case testcore.GetAction:
 | 
						|
			return true, nil, errors.NewNotFound(schema.GroupResource{}, "")
 | 
						|
		case testcore.DeleteAction:
 | 
						|
			return true, nil, nil
 | 
						|
		}
 | 
						|
		return false, nil, nil
 | 
						|
	})
 | 
						|
 | 
						|
	err := Rename(fake.CoreV1(), rcExisting, rc.Name)
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
	for j, action := range fake.Actions() {
 | 
						|
		if e, a := expectedActions[j], action.GetVerb(); e != a {
 | 
						|
			t.Errorf("unexpected action: expected %s, got %s", e, a)
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func TestFindSourceController(t *testing.T) {
 | 
						|
	ctrl1 := corev1.ReplicationController{
 | 
						|
		ObjectMeta: metav1.ObjectMeta{
 | 
						|
			Namespace: metav1.NamespaceDefault,
 | 
						|
			Name:      "foo",
 | 
						|
			Annotations: map[string]string{
 | 
						|
				sourceIDAnnotation: "bar:1234",
 | 
						|
			},
 | 
						|
		},
 | 
						|
	}
 | 
						|
	ctrl2 := corev1.ReplicationController{
 | 
						|
		ObjectMeta: metav1.ObjectMeta{
 | 
						|
			Namespace: metav1.NamespaceDefault,
 | 
						|
			Name:      "bar",
 | 
						|
			Annotations: map[string]string{
 | 
						|
				sourceIDAnnotation: "foo:12345",
 | 
						|
			},
 | 
						|
		},
 | 
						|
	}
 | 
						|
	ctrl3 := corev1.ReplicationController{
 | 
						|
		ObjectMeta: metav1.ObjectMeta{
 | 
						|
			Namespace: metav1.NamespaceDefault,
 | 
						|
			Name:      "baz",
 | 
						|
			Annotations: map[string]string{
 | 
						|
				sourceIDAnnotation: "baz:45667",
 | 
						|
			},
 | 
						|
		},
 | 
						|
	}
 | 
						|
	tests := []struct {
 | 
						|
		list               *corev1.ReplicationControllerList
 | 
						|
		expectedController *corev1.ReplicationController
 | 
						|
		name               string
 | 
						|
		expectError        bool
 | 
						|
	}{
 | 
						|
		{
 | 
						|
			list:        &corev1.ReplicationControllerList{},
 | 
						|
			expectError: true,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			list: &corev1.ReplicationControllerList{
 | 
						|
				Items: []corev1.ReplicationController{ctrl1},
 | 
						|
			},
 | 
						|
			name:        "foo",
 | 
						|
			expectError: true,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			list: &corev1.ReplicationControllerList{
 | 
						|
				Items: []corev1.ReplicationController{ctrl1},
 | 
						|
			},
 | 
						|
			name:               "bar",
 | 
						|
			expectedController: &ctrl1,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			list: &corev1.ReplicationControllerList{
 | 
						|
				Items: []corev1.ReplicationController{ctrl1, ctrl2},
 | 
						|
			},
 | 
						|
			name:               "bar",
 | 
						|
			expectedController: &ctrl1,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			list: &corev1.ReplicationControllerList{
 | 
						|
				Items: []corev1.ReplicationController{ctrl1, ctrl2},
 | 
						|
			},
 | 
						|
			name:               "foo",
 | 
						|
			expectedController: &ctrl2,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			list: &corev1.ReplicationControllerList{
 | 
						|
				Items: []corev1.ReplicationController{ctrl1, ctrl2, ctrl3},
 | 
						|
			},
 | 
						|
			name:               "baz",
 | 
						|
			expectedController: &ctrl3,
 | 
						|
		},
 | 
						|
	}
 | 
						|
	for _, tt := range tests {
 | 
						|
		t.Run(tt.name, func(t *testing.T) {
 | 
						|
			fakeClient := fake.NewSimpleClientset(tt.list)
 | 
						|
			ctrl, err := FindSourceController(fakeClient.CoreV1(), "default", tt.name)
 | 
						|
			if tt.expectError && err == nil {
 | 
						|
				t.Errorf("unexpected non-error")
 | 
						|
			}
 | 
						|
			if !tt.expectError && err != nil {
 | 
						|
				t.Errorf("unexpected error")
 | 
						|
			}
 | 
						|
			if !reflect.DeepEqual(ctrl, tt.expectedController) {
 | 
						|
				t.Errorf("expected:\n%v\ngot:\n%v\n", tt.expectedController, ctrl)
 | 
						|
			}
 | 
						|
		})
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func TestUpdateExistingReplicationController(t *testing.T) {
 | 
						|
	tests := []struct {
 | 
						|
		rc              *corev1.ReplicationController
 | 
						|
		name            string
 | 
						|
		deploymentKey   string
 | 
						|
		deploymentValue string
 | 
						|
 | 
						|
		expectedRc *corev1.ReplicationController
 | 
						|
		expectErr  bool
 | 
						|
	}{
 | 
						|
		{
 | 
						|
			rc: &corev1.ReplicationController{
 | 
						|
				ObjectMeta: metav1.ObjectMeta{
 | 
						|
					Namespace: metav1.NamespaceDefault,
 | 
						|
					Name:      "foo",
 | 
						|
				},
 | 
						|
				Spec: corev1.ReplicationControllerSpec{
 | 
						|
					Template: &corev1.PodTemplateSpec{},
 | 
						|
				},
 | 
						|
			},
 | 
						|
			name:            "foo",
 | 
						|
			deploymentKey:   "dk",
 | 
						|
			deploymentValue: "some-hash",
 | 
						|
 | 
						|
			expectedRc: &corev1.ReplicationController{
 | 
						|
				ObjectMeta: metav1.ObjectMeta{
 | 
						|
					Namespace: metav1.NamespaceDefault,
 | 
						|
					Name:      "foo",
 | 
						|
					Annotations: map[string]string{
 | 
						|
						"kubectl.kubernetes.io/next-controller-id": "foo",
 | 
						|
					},
 | 
						|
				},
 | 
						|
				Spec: corev1.ReplicationControllerSpec{
 | 
						|
					Selector: map[string]string{
 | 
						|
						"dk": "some-hash",
 | 
						|
					},
 | 
						|
					Template: &corev1.PodTemplateSpec{
 | 
						|
						ObjectMeta: metav1.ObjectMeta{
 | 
						|
							Labels: map[string]string{
 | 
						|
								"dk": "some-hash",
 | 
						|
							},
 | 
						|
						},
 | 
						|
					},
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			rc: &corev1.ReplicationController{
 | 
						|
				ObjectMeta: metav1.ObjectMeta{
 | 
						|
					Namespace: metav1.NamespaceDefault,
 | 
						|
					Name:      "foo",
 | 
						|
				},
 | 
						|
				Spec: corev1.ReplicationControllerSpec{
 | 
						|
					Template: &corev1.PodTemplateSpec{
 | 
						|
						ObjectMeta: metav1.ObjectMeta{
 | 
						|
							Labels: map[string]string{
 | 
						|
								"dk": "some-other-hash",
 | 
						|
							},
 | 
						|
						},
 | 
						|
					},
 | 
						|
					Selector: map[string]string{
 | 
						|
						"dk": "some-other-hash",
 | 
						|
					},
 | 
						|
				},
 | 
						|
			},
 | 
						|
			name:            "foo",
 | 
						|
			deploymentKey:   "dk",
 | 
						|
			deploymentValue: "some-hash",
 | 
						|
 | 
						|
			expectedRc: &corev1.ReplicationController{
 | 
						|
				ObjectMeta: metav1.ObjectMeta{
 | 
						|
					Namespace: metav1.NamespaceDefault,
 | 
						|
					Name:      "foo",
 | 
						|
					Annotations: map[string]string{
 | 
						|
						"kubectl.kubernetes.io/next-controller-id": "foo",
 | 
						|
					},
 | 
						|
				},
 | 
						|
				Spec: corev1.ReplicationControllerSpec{
 | 
						|
					Selector: map[string]string{
 | 
						|
						"dk": "some-other-hash",
 | 
						|
					},
 | 
						|
					Template: &corev1.PodTemplateSpec{
 | 
						|
						ObjectMeta: metav1.ObjectMeta{
 | 
						|
							Labels: map[string]string{
 | 
						|
								"dk": "some-other-hash",
 | 
						|
							},
 | 
						|
						},
 | 
						|
					},
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
	}
 | 
						|
	for _, tt := range tests {
 | 
						|
		t.Run(tt.name, func(t *testing.T) {
 | 
						|
			buffer := &bytes.Buffer{}
 | 
						|
			fakeClient := fake.NewSimpleClientset(tt.expectedRc)
 | 
						|
			rc, err := UpdateExistingReplicationController(fakeClient.CoreV1(), fakeClient.CoreV1(), tt.rc, "default", tt.name, tt.deploymentKey, tt.deploymentValue, buffer)
 | 
						|
			if !reflect.DeepEqual(rc, tt.expectedRc) {
 | 
						|
				t.Errorf("expected:\n%#v\ngot:\n%#v\n", tt.expectedRc, rc)
 | 
						|
			}
 | 
						|
			if tt.expectErr && err == nil {
 | 
						|
				t.Errorf("unexpected non-error")
 | 
						|
			}
 | 
						|
			if !tt.expectErr && err != nil {
 | 
						|
				t.Errorf("unexpected error: %v", err)
 | 
						|
			}
 | 
						|
		})
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func TestUpdateRcWithRetries(t *testing.T) {
 | 
						|
	codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
 | 
						|
	one := int32(1)
 | 
						|
	grace := int64(30)
 | 
						|
	enableServiceLinks := corev1.DefaultEnableServiceLinks
 | 
						|
	rc := &corev1.ReplicationController{
 | 
						|
		TypeMeta: metav1.TypeMeta{
 | 
						|
			APIVersion: "v1",
 | 
						|
			Kind:       "ReplicationController",
 | 
						|
		},
 | 
						|
		ObjectMeta: metav1.ObjectMeta{Name: "rc",
 | 
						|
			Labels: map[string]string{
 | 
						|
				"foo": "bar",
 | 
						|
			},
 | 
						|
		},
 | 
						|
		Spec: corev1.ReplicationControllerSpec{
 | 
						|
			Replicas: &one,
 | 
						|
			Selector: map[string]string{
 | 
						|
				"foo": "bar",
 | 
						|
			},
 | 
						|
			Template: &corev1.PodTemplateSpec{
 | 
						|
				ObjectMeta: metav1.ObjectMeta{
 | 
						|
					Labels: map[string]string{
 | 
						|
						"foo": "bar",
 | 
						|
					},
 | 
						|
				},
 | 
						|
				Spec: corev1.PodSpec{
 | 
						|
					RestartPolicy:                 corev1.RestartPolicyAlways,
 | 
						|
					DNSPolicy:                     corev1.DNSClusterFirst,
 | 
						|
					TerminationGracePeriodSeconds: &grace,
 | 
						|
					SecurityContext:               &corev1.PodSecurityContext{},
 | 
						|
					EnableServiceLinks:            &enableServiceLinks,
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
	}
 | 
						|
	rc.Spec.Template.Spec.SchedulerName = "default-scheduler"
 | 
						|
 | 
						|
	// Test end to end updating of the rc with retries. Essentially make sure the update handler
 | 
						|
	// sees the right updates, failures in update/get are handled properly, and that the updated
 | 
						|
	// rc with new resource version is returned to the caller. Without any of these rollingupdate
 | 
						|
	// will fail cryptically.
 | 
						|
	newRc := *rc
 | 
						|
	newRc.ResourceVersion = "2"
 | 
						|
	newRc.Spec.Selector["baz"] = "foobar"
 | 
						|
	header := http.Header{}
 | 
						|
	header.Set("Content-Type", runtime.ContentTypeJSON)
 | 
						|
	updates := []*http.Response{
 | 
						|
		{StatusCode: 409, Header: header, Body: objBody(codec, &corev1.ReplicationController{})}, // conflict
 | 
						|
		{StatusCode: 409, Header: header, Body: objBody(codec, &corev1.ReplicationController{})}, // conflict
 | 
						|
		{StatusCode: 200, Header: header, Body: objBody(codec, &newRc)},
 | 
						|
	}
 | 
						|
	gets := []*http.Response{
 | 
						|
		{StatusCode: 500, Header: header, Body: objBody(codec, &corev1.ReplicationController{})},
 | 
						|
		{StatusCode: 200, Header: header, Body: objBody(codec, rc)},
 | 
						|
	}
 | 
						|
	fakeClient := &manualfake.RESTClient{
 | 
						|
		GroupVersion:         corev1.SchemeGroupVersion,
 | 
						|
		NegotiatedSerializer: scheme.Codecs,
 | 
						|
		Client: manualfake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
 | 
						|
			switch p, m := req.URL.Path, req.Method; {
 | 
						|
			case p == "/api/v1/namespaces/default/replicationcontrollers/rc" && m == "PUT":
 | 
						|
				update := updates[0]
 | 
						|
				updates = updates[1:]
 | 
						|
				// We should always get an update with a valid rc even when the get fails. The rc should always
 | 
						|
				// contain the update.
 | 
						|
				if c, ok := readOrDie(t, req, codec).(*corev1.ReplicationController); !ok || !apiequality.Semantic.DeepEqual(rc, c) {
 | 
						|
					t.Errorf("Unexpected update body, got %+v expected %+v", c, rc)
 | 
						|
					t.Error(diff.ObjectDiff(rc, c))
 | 
						|
				} else if sel, ok := c.Spec.Selector["baz"]; !ok || sel != "foobar" {
 | 
						|
					t.Errorf("Expected selector label update, got %+v", c.Spec.Selector)
 | 
						|
				} else {
 | 
						|
					delete(c.Spec.Selector, "baz")
 | 
						|
				}
 | 
						|
				return update, nil
 | 
						|
			case p == "/api/v1/namespaces/default/replicationcontrollers/rc" && m == "GET":
 | 
						|
				get := gets[0]
 | 
						|
				gets = gets[1:]
 | 
						|
				return get, nil
 | 
						|
			default:
 | 
						|
				t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
 | 
						|
				return nil, nil
 | 
						|
			}
 | 
						|
		}),
 | 
						|
	}
 | 
						|
	clientConfig := &restclient.Config{
 | 
						|
		APIPath: "/api",
 | 
						|
		ContentConfig: restclient.ContentConfig{
 | 
						|
			NegotiatedSerializer: scheme.Codecs,
 | 
						|
			GroupVersion:         &corev1.SchemeGroupVersion,
 | 
						|
		},
 | 
						|
	}
 | 
						|
	restClient, _ := restclient.RESTClientFor(clientConfig)
 | 
						|
	restClient.Client = fakeClient.Client
 | 
						|
	clientset := kubernetes.New(restClient)
 | 
						|
 | 
						|
	if rc, err := updateRcWithRetries(
 | 
						|
		clientset.CoreV1(), "default", rc, func(c *corev1.ReplicationController) {
 | 
						|
			c.Spec.Selector["baz"] = "foobar"
 | 
						|
		}); err != nil {
 | 
						|
		t.Errorf("unexpected error: %v", err)
 | 
						|
	} else if sel, ok := rc.Spec.Selector["baz"]; !ok || sel != "foobar" || rc.ResourceVersion != "2" {
 | 
						|
		t.Errorf("Expected updated rc, got %+v", rc)
 | 
						|
	}
 | 
						|
	if len(updates) != 0 || len(gets) != 0 {
 | 
						|
		t.Errorf("Remaining updates %#v gets %#v", updates, gets)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func readOrDie(t *testing.T, req *http.Request, codec runtime.Codec) runtime.Object {
 | 
						|
	data, err := ioutil.ReadAll(req.Body)
 | 
						|
	if err != nil {
 | 
						|
		t.Errorf("Error reading: %v", err)
 | 
						|
		t.FailNow()
 | 
						|
	}
 | 
						|
	codec2 := scheme.Codecs.UniversalDecoder(scheme.Scheme.PrioritizedVersionsAllGroups()...)
 | 
						|
	obj, err := runtime.Decode(codec2, data)
 | 
						|
	if err != nil {
 | 
						|
		t.Log(string(data))
 | 
						|
		t.Errorf("error decoding: %v", err)
 | 
						|
		t.FailNow()
 | 
						|
	}
 | 
						|
	return obj
 | 
						|
}
 | 
						|
 | 
						|
func objBody(codec runtime.Codec, obj runtime.Object) io.ReadCloser {
 | 
						|
	return ioutil.NopCloser(bytes.NewReader([]byte(runtime.EncodeOrDie(codec, obj))))
 | 
						|
}
 | 
						|
 | 
						|
func TestAddDeploymentHash(t *testing.T) {
 | 
						|
	buf := &bytes.Buffer{}
 | 
						|
	codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
 | 
						|
	rc := &corev1.ReplicationController{
 | 
						|
		ObjectMeta: metav1.ObjectMeta{Name: "rc"},
 | 
						|
		Spec: corev1.ReplicationControllerSpec{
 | 
						|
			Selector: map[string]string{
 | 
						|
				"foo": "bar",
 | 
						|
			},
 | 
						|
			Template: &corev1.PodTemplateSpec{
 | 
						|
				ObjectMeta: metav1.ObjectMeta{
 | 
						|
					Labels: map[string]string{
 | 
						|
						"foo": "bar",
 | 
						|
					},
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	podList := &corev1.PodList{
 | 
						|
		Items: []corev1.Pod{
 | 
						|
			{ObjectMeta: metav1.ObjectMeta{Name: "foo"}},
 | 
						|
			{ObjectMeta: metav1.ObjectMeta{Name: "bar"}},
 | 
						|
			{ObjectMeta: metav1.ObjectMeta{Name: "baz"}},
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	seen := sets.String{}
 | 
						|
	updatedRc := false
 | 
						|
	fakeClient := &manualfake.RESTClient{
 | 
						|
		GroupVersion:         corev1.SchemeGroupVersion,
 | 
						|
		NegotiatedSerializer: scheme.Codecs,
 | 
						|
		Client: manualfake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
 | 
						|
			header := http.Header{}
 | 
						|
			header.Set("Content-Type", runtime.ContentTypeJSON)
 | 
						|
			switch p, m := req.URL.Path, req.Method; {
 | 
						|
			case p == "/api/v1/namespaces/default/pods" && m == "GET":
 | 
						|
				if req.URL.RawQuery != "labelSelector=foo%3Dbar" {
 | 
						|
					t.Errorf("Unexpected query string: %s", req.URL.RawQuery)
 | 
						|
				}
 | 
						|
				return &http.Response{StatusCode: 200, Header: header, Body: objBody(codec, podList)}, nil
 | 
						|
			case p == "/api/v1/namespaces/default/pods/foo" && m == "PUT":
 | 
						|
				seen.Insert("foo")
 | 
						|
				obj := readOrDie(t, req, codec)
 | 
						|
				podList.Items[0] = *(obj.(*corev1.Pod))
 | 
						|
				return &http.Response{StatusCode: 200, Header: header, Body: objBody(codec, &podList.Items[0])}, nil
 | 
						|
			case p == "/api/v1/namespaces/default/pods/bar" && m == "PUT":
 | 
						|
				seen.Insert("bar")
 | 
						|
				obj := readOrDie(t, req, codec)
 | 
						|
				podList.Items[1] = *(obj.(*corev1.Pod))
 | 
						|
				return &http.Response{StatusCode: 200, Header: header, Body: objBody(codec, &podList.Items[1])}, nil
 | 
						|
			case p == "/api/v1/namespaces/default/pods/baz" && m == "PUT":
 | 
						|
				seen.Insert("baz")
 | 
						|
				obj := readOrDie(t, req, codec)
 | 
						|
				podList.Items[2] = *(obj.(*corev1.Pod))
 | 
						|
				return &http.Response{StatusCode: 200, Header: header, Body: objBody(codec, &podList.Items[2])}, nil
 | 
						|
			case p == "/api/v1/namespaces/default/replicationcontrollers/rc" && m == "PUT":
 | 
						|
				updatedRc = true
 | 
						|
				return &http.Response{StatusCode: 200, Header: header, Body: objBody(codec, rc)}, nil
 | 
						|
			default:
 | 
						|
				t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
 | 
						|
				return nil, nil
 | 
						|
			}
 | 
						|
		}),
 | 
						|
	}
 | 
						|
	clientConfig := &restclient.Config{
 | 
						|
		APIPath: "/api",
 | 
						|
		ContentConfig: restclient.ContentConfig{
 | 
						|
			NegotiatedSerializer: scheme.Codecs,
 | 
						|
			GroupVersion:         &corev1.SchemeGroupVersion,
 | 
						|
		},
 | 
						|
	}
 | 
						|
	restClient, _ := restclient.RESTClientFor(clientConfig)
 | 
						|
	restClient.Client = fakeClient.Client
 | 
						|
	clientset := kubernetes.New(restClient)
 | 
						|
 | 
						|
	if _, err := AddDeploymentKeyToReplicationController(rc, clientset.CoreV1(), clientset.CoreV1(), "dk", "hash", metav1.NamespaceDefault, buf); err != nil {
 | 
						|
		t.Errorf("unexpected error: %v", err)
 | 
						|
	}
 | 
						|
	for _, pod := range podList.Items {
 | 
						|
		if !seen.Has(pod.Name) {
 | 
						|
			t.Errorf("Missing update for pod: %s", pod.Name)
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if !updatedRc {
 | 
						|
		t.Errorf("Failed to update replication controller with new labels")
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func TestRollingUpdater_readyPods(t *testing.T) {
 | 
						|
	count := 0
 | 
						|
	now := metav1.Date(2016, time.April, 1, 1, 0, 0, 0, time.UTC)
 | 
						|
	mkpod := func(owner *corev1.ReplicationController, ready bool, readyTime metav1.Time) *corev1.Pod {
 | 
						|
		count = count + 1
 | 
						|
		labels := map[string]string{}
 | 
						|
		for k, v := range owner.Spec.Selector {
 | 
						|
			labels[k] = v
 | 
						|
		}
 | 
						|
		status := corev1.ConditionTrue
 | 
						|
		if !ready {
 | 
						|
			status = corev1.ConditionFalse
 | 
						|
		}
 | 
						|
		return &corev1.Pod{
 | 
						|
			ObjectMeta: metav1.ObjectMeta{
 | 
						|
				Namespace: metav1.NamespaceDefault,
 | 
						|
				Name:      fmt.Sprintf("pod-%d", count),
 | 
						|
				Labels:    labels,
 | 
						|
			},
 | 
						|
			Status: corev1.PodStatus{
 | 
						|
				Conditions: []corev1.PodCondition{
 | 
						|
					{
 | 
						|
						Type:               corev1.PodReady,
 | 
						|
						Status:             status,
 | 
						|
						LastTransitionTime: readyTime,
 | 
						|
					},
 | 
						|
				},
 | 
						|
			},
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	tests := []struct {
 | 
						|
		name  string
 | 
						|
		oldRc *corev1.ReplicationController
 | 
						|
		newRc *corev1.ReplicationController
 | 
						|
		// expectated old/new ready counts
 | 
						|
		oldReady int32
 | 
						|
		newReady int32
 | 
						|
		// pods owned by the rcs; indicate whether they're ready
 | 
						|
		oldPods []bool
 | 
						|
		newPods []bool
 | 
						|
		// deletions - should be less then the size of the respective slice above
 | 
						|
		// e.g. len(oldPods) > oldPodDeletions && len(newPods) > newPodDeletions
 | 
						|
		oldPodDeletions int
 | 
						|
		newPodDeletions int
 | 
						|
		// specify additional time to wait for deployment to wait on top of the
 | 
						|
		// pod ready time
 | 
						|
		minReadySeconds int32
 | 
						|
		podReadyTimeFn  func() metav1.Time
 | 
						|
		nowFn           func() metav1.Time
 | 
						|
	}{
 | 
						|
		{
 | 
						|
			name:     "test1",
 | 
						|
			oldRc:    oldRc(4, 4),
 | 
						|
			newRc:    newRc(4, 4),
 | 
						|
			oldReady: 4,
 | 
						|
			newReady: 2,
 | 
						|
			oldPods: []bool{
 | 
						|
				true,
 | 
						|
				true,
 | 
						|
				true,
 | 
						|
				true,
 | 
						|
			},
 | 
						|
			newPods: []bool{
 | 
						|
				true,
 | 
						|
				false,
 | 
						|
				true,
 | 
						|
				false,
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:     "test2",
 | 
						|
			oldRc:    oldRc(4, 4),
 | 
						|
			newRc:    newRc(4, 4),
 | 
						|
			oldReady: 0,
 | 
						|
			newReady: 1,
 | 
						|
			oldPods: []bool{
 | 
						|
				false,
 | 
						|
			},
 | 
						|
			newPods: []bool{
 | 
						|
				true,
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:     "test3",
 | 
						|
			oldRc:    oldRc(4, 4),
 | 
						|
			newRc:    newRc(4, 4),
 | 
						|
			oldReady: 1,
 | 
						|
			newReady: 0,
 | 
						|
			oldPods: []bool{
 | 
						|
				true,
 | 
						|
			},
 | 
						|
			newPods: []bool{
 | 
						|
				false,
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:     "test4",
 | 
						|
			oldRc:    oldRc(4, 4),
 | 
						|
			newRc:    newRc(4, 4),
 | 
						|
			oldReady: 0,
 | 
						|
			newReady: 0,
 | 
						|
			oldPods: []bool{
 | 
						|
				true,
 | 
						|
			},
 | 
						|
			newPods: []bool{
 | 
						|
				true,
 | 
						|
			},
 | 
						|
			minReadySeconds: 5,
 | 
						|
			nowFn:           func() metav1.Time { return now },
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:     "test5",
 | 
						|
			oldRc:    oldRc(4, 4),
 | 
						|
			newRc:    newRc(4, 4),
 | 
						|
			oldReady: 1,
 | 
						|
			newReady: 1,
 | 
						|
			oldPods: []bool{
 | 
						|
				true,
 | 
						|
			},
 | 
						|
			newPods: []bool{
 | 
						|
				true,
 | 
						|
			},
 | 
						|
			minReadySeconds: 5,
 | 
						|
			nowFn:           func() metav1.Time { return metav1.Time{Time: now.Add(time.Duration(6 * time.Second))} },
 | 
						|
			podReadyTimeFn:  func() metav1.Time { return now },
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:     "test6",
 | 
						|
			oldRc:    oldRc(4, 4),
 | 
						|
			newRc:    newRc(4, 4),
 | 
						|
			oldReady: 2,
 | 
						|
			newReady: 0,
 | 
						|
			oldPods: []bool{
 | 
						|
				// All old pods are ready
 | 
						|
				true, true, true, true,
 | 
						|
			},
 | 
						|
			// Two of them have been marked for deletion though
 | 
						|
			oldPodDeletions: 2,
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	for i, tt := range tests {
 | 
						|
		t.Run(tt.name, func(t *testing.T) {
 | 
						|
			t.Logf("evaluating test %d", i)
 | 
						|
			if tt.nowFn == nil {
 | 
						|
				tt.nowFn = func() metav1.Time { return now }
 | 
						|
			}
 | 
						|
			if tt.podReadyTimeFn == nil {
 | 
						|
				tt.podReadyTimeFn = tt.nowFn
 | 
						|
			}
 | 
						|
			// Populate the fake client with pods associated with their owners.
 | 
						|
			pods := []runtime.Object{}
 | 
						|
			for _, ready := range tt.oldPods {
 | 
						|
				pod := mkpod(tt.oldRc, ready, tt.podReadyTimeFn())
 | 
						|
				if tt.oldPodDeletions > 0 {
 | 
						|
					now := metav1.Now()
 | 
						|
					pod.DeletionTimestamp = &now
 | 
						|
					tt.oldPodDeletions--
 | 
						|
				}
 | 
						|
				pods = append(pods, pod)
 | 
						|
			}
 | 
						|
			for _, ready := range tt.newPods {
 | 
						|
				pod := mkpod(tt.newRc, ready, tt.podReadyTimeFn())
 | 
						|
				if tt.newPodDeletions > 0 {
 | 
						|
					now := metav1.Now()
 | 
						|
					pod.DeletionTimestamp = &now
 | 
						|
					tt.newPodDeletions--
 | 
						|
				}
 | 
						|
				pods = append(pods, pod)
 | 
						|
			}
 | 
						|
			client := fake.NewSimpleClientset(pods...)
 | 
						|
 | 
						|
			updater := &RollingUpdater{
 | 
						|
				ns:        "default",
 | 
						|
				rcClient:  client.CoreV1(),
 | 
						|
				podClient: client.CoreV1(),
 | 
						|
				nowFn:     tt.nowFn,
 | 
						|
			}
 | 
						|
			oldReady, newReady, err := updater.readyPods(tt.oldRc, tt.newRc, tt.minReadySeconds)
 | 
						|
			if err != nil {
 | 
						|
				t.Errorf("unexpected error: %v", err)
 | 
						|
			}
 | 
						|
			if e, a := tt.oldReady, oldReady; e != a {
 | 
						|
				t.Errorf("expected old ready %d, got %d", e, a)
 | 
						|
			}
 | 
						|
			if e, a := tt.newReady, newReady; e != a {
 | 
						|
				t.Errorf("expected new ready %d, got %d", e, a)
 | 
						|
			}
 | 
						|
		})
 | 
						|
	}
 | 
						|
}
 |