Browse code

Port upstream rolling updater enhancements

Port the upstream rolling updater enhancements to origin, and
deprecate the updatePercent rolling parameter.

Dan Mace authored on 2015/09/09 22:22:20
Showing 25 changed files
... ...
@@ -24,3 +24,8 @@ when that change will happen.
24 24
 (with the `namespace` field set) or `/ns/namespace-name/localsubjectaccessreview`.  In 
25 25
 Origin 1.y / OSE 3.y, support for `/ns/namespace-name/subjectaccessreview` wil be removed.
26 26
 At that time, the openshift docker registry image must be upgraded in order to continue functioning.
27
+
28
+1. The `deploymentConfig.rollingParams.updatePercent` field is deprecated in
29
+  favor of `deploymentConfig.rollingParams.maxUnavailable` and
30
+  `deploymentConfig.rollingParams.maxSurge`. The `updatePercent` field will be
31
+  removed  in Origin 1.1 (OSE 3.1).
... ...
@@ -15071,6 +15071,14 @@
15071 15071
       "format": "int64",
15072 15072
       "description": "the time to wait for updates before giving up"
15073 15073
      },
15074
+     "maxUnavailable": {
15075
+      "type": "string",
15076
+      "description": "max number of pods that can be unavailable during the update; value can be an absolute number or a percentage of total pods at start of update"
15077
+     },
15078
+     "maxSurge": {
15079
+      "type": "string",
15080
+      "description": "max number of pods that can be scheduled above the original number of pods; value can be an absolute number or a percentage of total pods at start of update"
15081
+     },
15074 15082
      "updatePercent": {
15075 15083
       "type": "integer",
15076 15084
       "format": "int32",
... ...
@@ -1481,6 +1481,16 @@ func deepCopy_api_RollingDeploymentStrategyParams(in deployapi.RollingDeployment
1481 1481
 	} else {
1482 1482
 		out.TimeoutSeconds = nil
1483 1483
 	}
1484
+	if newVal, err := c.DeepCopy(in.MaxUnavailable); err != nil {
1485
+		return err
1486
+	} else {
1487
+		out.MaxUnavailable = newVal.(util.IntOrString)
1488
+	}
1489
+	if newVal, err := c.DeepCopy(in.MaxSurge); err != nil {
1490
+		return err
1491
+	} else {
1492
+		out.MaxSurge = newVal.(util.IntOrString)
1493
+	}
1484 1494
 	if in.UpdatePercent != nil {
1485 1495
 		out.UpdatePercent = new(int)
1486 1496
 		*out.UpdatePercent = *in.UpdatePercent
... ...
@@ -206,25 +206,27 @@ func fuzzInternalObject(t *testing.T, forVersion string, item runtime.Object, se
206 206
 		},
207 207
 		func(j *deploy.DeploymentStrategy, c fuzz.Continue) {
208 208
 			c.FuzzNoCustom(j)
209
-			mkintp := func(i int) *int64 {
210
-				v := int64(i)
211
-				return &v
212
-			}
213
-			switch c.Intn(3) {
214
-			case 0:
215
-				// TODO: we should not have to set defaults, instead we should be able
216
-				// to detect defaults were applied.
217
-				j.Type = deploy.DeploymentStrategyTypeRolling
218
-				j.RollingParams = &deploy.RollingDeploymentStrategyParams{
219
-					IntervalSeconds:     mkintp(1),
220
-					UpdatePeriodSeconds: mkintp(1),
221
-					TimeoutSeconds:      mkintp(120),
209
+			strategyTypes := []deploy.DeploymentStrategyType{deploy.DeploymentStrategyTypeRecreate, deploy.DeploymentStrategyTypeRolling, deploy.DeploymentStrategyTypeCustom}
210
+			j.Type = strategyTypes[c.Rand.Intn(len(strategyTypes))]
211
+			switch j.Type {
212
+			case deploy.DeploymentStrategyTypeRolling:
213
+				params := &deploy.RollingDeploymentStrategyParams{}
214
+				randInt64 := func() *int64 {
215
+					p := int64(c.RandUint64())
216
+					return &p
222 217
 				}
223
-			case 1:
224
-				j.Type = deploy.DeploymentStrategyTypeRecreate
225
-				j.RollingParams = nil
226
-			case 2:
227
-				j.Type = deploy.DeploymentStrategyTypeCustom
218
+				params.TimeoutSeconds = randInt64()
219
+				params.IntervalSeconds = randInt64()
220
+				params.UpdatePeriodSeconds = randInt64()
221
+				if c.RandBool() {
222
+					params.MaxUnavailable = util.NewIntOrStringFromInt(int(c.RandUint64()))
223
+					params.MaxSurge = util.NewIntOrStringFromInt(int(c.RandUint64()))
224
+				} else {
225
+					params.MaxSurge = util.NewIntOrStringFromString(fmt.Sprintf("%d%%", c.RandUint64()))
226
+					params.MaxUnavailable = util.NewIntOrStringFromString(fmt.Sprintf("%d%%", c.RandUint64()))
227
+				}
228
+				j.RollingParams = params
229
+			default:
228 230
 				j.RollingParams = nil
229 231
 			}
230 232
 		},
... ...
@@ -1527,6 +1527,24 @@ func deepCopy_v1_RollingDeploymentStrategyParams(in deployapiv1.RollingDeploymen
1527 1527
 	} else {
1528 1528
 		out.TimeoutSeconds = nil
1529 1529
 	}
1530
+	if in.MaxUnavailable != nil {
1531
+		if newVal, err := c.DeepCopy(in.MaxUnavailable); err != nil {
1532
+			return err
1533
+		} else {
1534
+			out.MaxUnavailable = newVal.(*util.IntOrString)
1535
+		}
1536
+	} else {
1537
+		out.MaxUnavailable = nil
1538
+	}
1539
+	if in.MaxSurge != nil {
1540
+		if newVal, err := c.DeepCopy(in.MaxSurge); err != nil {
1541
+			return err
1542
+		} else {
1543
+			out.MaxSurge = newVal.(*util.IntOrString)
1544
+		}
1545
+	} else {
1546
+		out.MaxSurge = nil
1547
+	}
1530 1548
 	if in.UpdatePercent != nil {
1531 1549
 		out.UpdatePercent = new(int)
1532 1550
 		*out.UpdatePercent = *in.UpdatePercent
... ...
@@ -1535,6 +1535,24 @@ func deepCopy_v1beta3_RollingDeploymentStrategyParams(in deployapiv1beta3.Rollin
1535 1535
 	} else {
1536 1536
 		out.TimeoutSeconds = nil
1537 1537
 	}
1538
+	if in.MaxUnavailable != nil {
1539
+		if newVal, err := c.DeepCopy(in.MaxUnavailable); err != nil {
1540
+			return err
1541
+		} else {
1542
+			out.MaxUnavailable = newVal.(*util.IntOrString)
1543
+		}
1544
+	} else {
1545
+		out.MaxUnavailable = nil
1546
+	}
1547
+	if in.MaxSurge != nil {
1548
+		if newVal, err := c.DeepCopy(in.MaxSurge); err != nil {
1549
+			return err
1550
+		} else {
1551
+			out.MaxSurge = newVal.(*util.IntOrString)
1552
+		}
1553
+	} else {
1554
+		out.MaxSurge = nil
1555
+	}
1538 1556
 	if in.UpdatePercent != nil {
1539 1557
 		out.UpdatePercent = new(int)
1540 1558
 		*out.UpdatePercent = *in.UpdatePercent
... ...
@@ -485,7 +485,7 @@ func RunCmdRouter(f *clientcmd.Factory, cmd *cobra.Command, out io.Writer, cfg *
485 485
 			"STATS_PASSWORD":                     cfg.StatsPassword,
486 486
 		}
487 487
 
488
-		updatePercent := int(-10)
488
+		updatePercent := int(-25)
489 489
 
490 490
 		secrets, volumes, mounts, err := generateSecretsConfig(cfg, kClient,
491 491
 			namespace)
... ...
@@ -125,10 +125,18 @@ func TestCmdDeploy_retryOk(t *testing.T) {
125 125
 	existingDeployment.Annotations[deployapi.DeploymentCancelledAnnotation] = deployapi.DeploymentCancelledAnnotationValue
126 126
 	existingDeployment.Annotations[deployapi.DeploymentStatusReasonAnnotation] = deployapi.DeploymentCancelledByUser
127 127
 
128
+	mkpod := func(name string) kapi.Pod {
129
+		return kapi.Pod{
130
+			ObjectMeta: kapi.ObjectMeta{
131
+				Name: name,
132
+				Labels: map[string]string{
133
+					deployapi.DeployerPodForDeploymentLabel: existingDeployment.Name,
134
+				},
135
+			},
136
+		}
137
+	}
128 138
 	existingDeployerPods := []kapi.Pod{
129
-		{ObjectMeta: kapi.ObjectMeta{Name: "prehook"}},
130
-		{ObjectMeta: kapi.ObjectMeta{Name: "posthook"}},
131
-		{ObjectMeta: kapi.ObjectMeta{Name: "deployerpod"}},
139
+		mkpod("prehook"), mkpod("posthook"), mkpod("deployerpod"),
132 140
 	}
133 141
 
134 142
 	kubeClient := &ktc.Fake{}
... ...
@@ -2,6 +2,7 @@ package api
2 2
 
3 3
 import (
4 4
 	kapi "k8s.io/kubernetes/pkg/api"
5
+	kutil "k8s.io/kubernetes/pkg/util"
5 6
 )
6 7
 
7 8
 // DeploymentStatus describes the possible states a deployment can be in.
... ...
@@ -115,8 +116,32 @@ type RollingDeploymentStrategyParams struct {
115 115
 	// TimeoutSeconds is the time to wait for updates before giving up. If the
116 116
 	// value is nil, a default will be used.
117 117
 	TimeoutSeconds *int64
118
+	// The maximum number of pods that can be unavailable during the update.
119
+	// Value can be an absolute number (ex: 5) or a percentage of total pods at the start of update (ex: 10%).
120
+	// Absolute number is calculated from percentage by rounding up.
121
+	// This can not be 0 if MaxSurge is 0.
122
+	// By default, a fixed value of 1 is used.
123
+	// Example: when this is set to 30%, the old RC can be scaled down by 30%
124
+	// immediately when the rolling update starts. Once new pods are ready, old RC
125
+	// can be scaled down further, followed by scaling up the new RC, ensuring
126
+	// that at least 70% of original number of pods are available at all times
127
+	// during the update.
128
+	MaxUnavailable kutil.IntOrString
129
+	// The maximum number of pods that can be scheduled above the original number of
130
+	// pods.
131
+	// Value can be an absolute number (ex: 5) or a percentage of total pods at
132
+	// the start of the update (ex: 10%). This can not be 0 if MaxUnavailable is 0.
133
+	// Absolute number is calculated from percentage by rounding up.
134
+	// By default, a value of 1 is used.
135
+	// Example: when this is set to 30%, the new RC can be scaled up by 30%
136
+	// immediately when the rolling update starts. Once old pods have been killed,
137
+	// new RC can be scaled up further, ensuring that total number of pods running
138
+	// at any time during the update is atmost 130% of original pods.
139
+	MaxSurge kutil.IntOrString
118 140
 	// UpdatePercent is the percentage of replicas to scale up or down each
119 141
 	// interval. If nil, one replica will be scaled up and down each interval.
142
+	// If negative, the scale order will be down/up instead of up/down.
143
+	// DEPRECATED: Use MaxUnavailable/MaxSurge instead.
120 144
 	UpdatePercent *int
121 145
 	// Pre is a lifecycle hook which is executed before the deployment process
122 146
 	// begins. All LifecycleHookFailurePolicy values are supported.
... ...
@@ -2,9 +2,11 @@ package v1
2 2
 
3 3
 import (
4 4
 	"fmt"
5
+	"math"
5 6
 
6 7
 	"k8s.io/kubernetes/pkg/api"
7 8
 	"k8s.io/kubernetes/pkg/conversion"
9
+	kutil "k8s.io/kubernetes/pkg/util"
8 10
 
9 11
 	newer "github.com/openshift/origin/pkg/deploy/api"
10 12
 	imageapi "github.com/openshift/origin/pkg/image/api"
... ...
@@ -196,6 +198,60 @@ func convert_api_DeploymentCauseImageTrigger_To_v1_DeploymentCauseImageTrigger(i
196 196
 	return nil
197 197
 }
198 198
 
199
+func convert_v1_RollingDeploymentStrategyParams_To_api_RollingDeploymentStrategyParams(in *RollingDeploymentStrategyParams, out *newer.RollingDeploymentStrategyParams, s conversion.Scope) error {
200
+	out.UpdatePeriodSeconds = in.UpdatePeriodSeconds
201
+	out.IntervalSeconds = in.IntervalSeconds
202
+	out.TimeoutSeconds = in.TimeoutSeconds
203
+	out.UpdatePercent = in.UpdatePercent
204
+
205
+	if in.UpdatePercent != nil {
206
+		pct := kutil.NewIntOrStringFromString(fmt.Sprintf("%d%%", int(math.Abs(float64(*in.UpdatePercent)))))
207
+		if *in.UpdatePercent > 0 {
208
+			out.MaxSurge = pct
209
+		} else {
210
+			out.MaxUnavailable = pct
211
+		}
212
+	} else {
213
+		if err := s.Convert(in.MaxUnavailable, &out.MaxUnavailable, 0); err != nil {
214
+			return err
215
+		}
216
+		if err := s.Convert(in.MaxSurge, &out.MaxSurge, 0); err != nil {
217
+			return err
218
+		}
219
+	}
220
+	return nil
221
+}
222
+
223
+func convert_api_RollingDeploymentStrategyParams_To_v1_RollingDeploymentStrategyParams(in *newer.RollingDeploymentStrategyParams, out *RollingDeploymentStrategyParams, s conversion.Scope) error {
224
+	out.UpdatePeriodSeconds = in.UpdatePeriodSeconds
225
+	out.IntervalSeconds = in.IntervalSeconds
226
+	out.TimeoutSeconds = in.TimeoutSeconds
227
+	out.UpdatePercent = in.UpdatePercent
228
+
229
+	if out.MaxUnavailable == nil {
230
+		out.MaxUnavailable = &kutil.IntOrString{}
231
+	}
232
+	if out.MaxSurge == nil {
233
+		out.MaxSurge = &kutil.IntOrString{}
234
+	}
235
+	if in.UpdatePercent != nil {
236
+		pct := kutil.NewIntOrStringFromString(fmt.Sprintf("%d%%", int(math.Abs(float64(*in.UpdatePercent)))))
237
+		if *in.UpdatePercent > 0 {
238
+			out.MaxSurge = &pct
239
+		} else {
240
+			out.MaxUnavailable = &pct
241
+		}
242
+	} else {
243
+		if err := s.Convert(&in.MaxUnavailable, out.MaxUnavailable, 0); err != nil {
244
+			return err
245
+		}
246
+		if err := s.Convert(&in.MaxSurge, out.MaxSurge, 0); err != nil {
247
+			return err
248
+		}
249
+	}
250
+	return nil
251
+}
252
+
199 253
 func init() {
200 254
 	err := api.Scheme.AddDefaultingFuncs(
201 255
 		func(obj *DeploymentTriggerImageChangeParams) {
... ...
@@ -222,6 +278,9 @@ func init() {
222 222
 
223 223
 		convert_v1_DeploymentCauseImageTrigger_To_api_DeploymentCauseImageTrigger,
224 224
 		convert_api_DeploymentCauseImageTrigger_To_v1_DeploymentCauseImageTrigger,
225
+
226
+		convert_v1_RollingDeploymentStrategyParams_To_api_RollingDeploymentStrategyParams,
227
+		convert_api_RollingDeploymentStrategyParams_To_v1_RollingDeploymentStrategyParams,
225 228
 	)
226 229
 	if err != nil {
227 230
 		panic(err)
... ...
@@ -1,31 +1,181 @@
1 1
 package v1
2 2
 
3 3
 import (
4
+	"reflect"
4 5
 	"testing"
5 6
 
6
-	"k8s.io/kubernetes/pkg/api"
7
-	kapi "k8s.io/kubernetes/pkg/api/v1"
7
+	kapi "k8s.io/kubernetes/pkg/api"
8
+	kapiv1 "k8s.io/kubernetes/pkg/api/v1"
9
+	"k8s.io/kubernetes/pkg/util"
8 10
 
9 11
 	newer "github.com/openshift/origin/pkg/deploy/api"
10 12
 )
11 13
 
12 14
 func TestTriggerRoundTrip(t *testing.T) {
13 15
 	p := DeploymentTriggerImageChangeParams{
14
-		From: kapi.ObjectReference{
16
+		From: kapiv1.ObjectReference{
15 17
 			Kind: "DockerImage",
16 18
 			Name: "",
17 19
 		},
18 20
 	}
19 21
 	out := &newer.DeploymentTriggerImageChangeParams{}
20
-	if err := api.Scheme.Convert(&p, out); err == nil {
22
+	if err := kapi.Scheme.Convert(&p, out); err == nil {
21 23
 		t.Errorf("unexpected error: %v", err)
22 24
 	}
23 25
 	p.From.Name = "a/b:test"
24 26
 	out = &newer.DeploymentTriggerImageChangeParams{}
25
-	if err := api.Scheme.Convert(&p, out); err != nil {
27
+	if err := kapi.Scheme.Convert(&p, out); err != nil {
26 28
 		t.Errorf("unexpected error: %v", err)
27 29
 	}
28 30
 	if out.RepositoryName != "a/b" && out.Tag != "test" {
29 31
 		t.Errorf("unexpected output: %#v", out)
30 32
 	}
31 33
 }
34
+
35
+func Test_convert_v1_RollingDeploymentStrategyParams_To_api_RollingDeploymentStrategyParams(t *testing.T) {
36
+	tests := []struct {
37
+		in  *RollingDeploymentStrategyParams
38
+		out *newer.RollingDeploymentStrategyParams
39
+	}{
40
+		{
41
+			in: &RollingDeploymentStrategyParams{
42
+				UpdatePeriodSeconds: newInt64(5),
43
+				IntervalSeconds:     newInt64(6),
44
+				TimeoutSeconds:      newInt64(7),
45
+				UpdatePercent:       newInt(-25),
46
+			},
47
+			out: &newer.RollingDeploymentStrategyParams{
48
+				UpdatePeriodSeconds: newInt64(5),
49
+				IntervalSeconds:     newInt64(6),
50
+				TimeoutSeconds:      newInt64(7),
51
+				UpdatePercent:       newInt(-25),
52
+				MaxSurge:            util.NewIntOrStringFromInt(0),
53
+				MaxUnavailable:      util.NewIntOrStringFromString("25%"),
54
+			},
55
+		},
56
+		{
57
+			in: &RollingDeploymentStrategyParams{
58
+				UpdatePeriodSeconds: newInt64(5),
59
+				IntervalSeconds:     newInt64(6),
60
+				TimeoutSeconds:      newInt64(7),
61
+				UpdatePercent:       newInt(25),
62
+			},
63
+			out: &newer.RollingDeploymentStrategyParams{
64
+				UpdatePeriodSeconds: newInt64(5),
65
+				IntervalSeconds:     newInt64(6),
66
+				TimeoutSeconds:      newInt64(7),
67
+				UpdatePercent:       newInt(25),
68
+				MaxSurge:            util.NewIntOrStringFromString("25%"),
69
+				MaxUnavailable:      util.NewIntOrStringFromInt(0),
70
+			},
71
+		},
72
+		{
73
+			in: &RollingDeploymentStrategyParams{
74
+				UpdatePeriodSeconds: newInt64(5),
75
+				IntervalSeconds:     newInt64(6),
76
+				TimeoutSeconds:      newInt64(7),
77
+				MaxSurge:            newIntOrString(util.NewIntOrStringFromInt(10)),
78
+				MaxUnavailable:      newIntOrString(util.NewIntOrStringFromInt(20)),
79
+			},
80
+			out: &newer.RollingDeploymentStrategyParams{
81
+				UpdatePeriodSeconds: newInt64(5),
82
+				IntervalSeconds:     newInt64(6),
83
+				TimeoutSeconds:      newInt64(7),
84
+				MaxSurge:            util.NewIntOrStringFromInt(10),
85
+				MaxUnavailable:      util.NewIntOrStringFromInt(20),
86
+			},
87
+		},
88
+	}
89
+
90
+	for _, test := range tests {
91
+		out := &newer.RollingDeploymentStrategyParams{}
92
+		if err := kapi.Scheme.Convert(test.in, out); err != nil {
93
+			t.Errorf("unexpected error: %v", err)
94
+		}
95
+		if !reflect.DeepEqual(out, test.out) {
96
+			t.Errorf("got different than expected:\nA:\t%#v\nB:\t%#v\n\nDiff:\n%s\n\n%s", out, test.out, util.ObjectDiff(test.out, out), util.ObjectGoPrintSideBySide(test.out, out))
97
+		}
98
+	}
99
+}
100
+
101
+func Test_convert_api_RollingDeploymentStrategyParams_To_v1_RollingDeploymentStrategyParams(t *testing.T) {
102
+	tests := []struct {
103
+		in  *newer.RollingDeploymentStrategyParams
104
+		out *RollingDeploymentStrategyParams
105
+	}{
106
+		{
107
+			in: &newer.RollingDeploymentStrategyParams{
108
+				UpdatePeriodSeconds: newInt64(5),
109
+				IntervalSeconds:     newInt64(6),
110
+				TimeoutSeconds:      newInt64(7),
111
+				UpdatePercent:       newInt(-25),
112
+				MaxSurge:            util.NewIntOrStringFromInt(0),
113
+				MaxUnavailable:      util.NewIntOrStringFromString("25%"),
114
+			},
115
+			out: &RollingDeploymentStrategyParams{
116
+				UpdatePeriodSeconds: newInt64(5),
117
+				IntervalSeconds:     newInt64(6),
118
+				TimeoutSeconds:      newInt64(7),
119
+				UpdatePercent:       newInt(-25),
120
+				MaxSurge:            newIntOrString(util.NewIntOrStringFromInt(0)),
121
+				MaxUnavailable:      newIntOrString(util.NewIntOrStringFromString("25%")),
122
+			},
123
+		},
124
+		{
125
+			in: &newer.RollingDeploymentStrategyParams{
126
+				UpdatePeriodSeconds: newInt64(5),
127
+				IntervalSeconds:     newInt64(6),
128
+				TimeoutSeconds:      newInt64(7),
129
+				UpdatePercent:       newInt(25),
130
+				MaxSurge:            util.NewIntOrStringFromString("25%"),
131
+				MaxUnavailable:      util.NewIntOrStringFromInt(0),
132
+			},
133
+			out: &RollingDeploymentStrategyParams{
134
+				UpdatePeriodSeconds: newInt64(5),
135
+				IntervalSeconds:     newInt64(6),
136
+				TimeoutSeconds:      newInt64(7),
137
+				UpdatePercent:       newInt(25),
138
+				MaxSurge:            newIntOrString(util.NewIntOrStringFromString("25%")),
139
+				MaxUnavailable:      newIntOrString(util.NewIntOrStringFromInt(0)),
140
+			},
141
+		},
142
+		{
143
+			in: &newer.RollingDeploymentStrategyParams{
144
+				UpdatePeriodSeconds: newInt64(5),
145
+				IntervalSeconds:     newInt64(6),
146
+				TimeoutSeconds:      newInt64(7),
147
+				MaxSurge:            util.NewIntOrStringFromInt(10),
148
+				MaxUnavailable:      util.NewIntOrStringFromInt(20),
149
+			},
150
+			out: &RollingDeploymentStrategyParams{
151
+				UpdatePeriodSeconds: newInt64(5),
152
+				IntervalSeconds:     newInt64(6),
153
+				TimeoutSeconds:      newInt64(7),
154
+				MaxSurge:            newIntOrString(util.NewIntOrStringFromInt(10)),
155
+				MaxUnavailable:      newIntOrString(util.NewIntOrStringFromInt(20)),
156
+			},
157
+		},
158
+	}
159
+
160
+	for _, test := range tests {
161
+		out := &RollingDeploymentStrategyParams{}
162
+		if err := kapi.Scheme.Convert(test.in, out); err != nil {
163
+			t.Errorf("unexpected error: %v", err)
164
+		}
165
+		if !reflect.DeepEqual(out, test.out) {
166
+			t.Errorf("got different than expected:\nA:\t%#v\nB:\t%#v\n\nDiff:\n%s\n\n%s", out, test.out, util.ObjectDiff(test.out, out), util.ObjectGoPrintSideBySide(test.out, out))
167
+		}
168
+	}
169
+}
170
+
171
+func newInt64(val int64) *int64 {
172
+	return &val
173
+}
174
+
175
+func newInt(val int) *int {
176
+	return &val
177
+}
178
+
179
+func newIntOrString(ios util.IntOrString) *util.IntOrString {
180
+	return &ios
181
+}
... ...
@@ -2,6 +2,7 @@ package v1
2 2
 
3 3
 import (
4 4
 	"k8s.io/kubernetes/pkg/api"
5
+	kutil "k8s.io/kubernetes/pkg/util"
5 6
 
6 7
 	deployapi "github.com/openshift/origin/pkg/deploy/api"
7 8
 )
... ...
@@ -44,6 +45,18 @@ func init() {
44 44
 			if obj.TimeoutSeconds == nil {
45 45
 				obj.TimeoutSeconds = mkintp(deployapi.DefaultRollingTimeoutSeconds)
46 46
 			}
47
+
48
+			if obj.UpdatePercent == nil {
49
+				// Apply defaults.
50
+				if obj.MaxUnavailable == nil {
51
+					maxUnavailable := kutil.NewIntOrStringFromString("25%")
52
+					obj.MaxUnavailable = &maxUnavailable
53
+				}
54
+				if obj.MaxSurge == nil {
55
+					maxSurge := kutil.NewIntOrStringFromString("25%")
56
+					obj.MaxSurge = &maxSurge
57
+				}
58
+			}
47 59
 		},
48 60
 	)
49 61
 	if err != nil {
... ...
@@ -4,15 +4,184 @@ import (
4 4
 	"reflect"
5 5
 	"testing"
6 6
 
7
+	kapi "k8s.io/kubernetes/pkg/api"
7 8
 	"k8s.io/kubernetes/pkg/runtime"
9
+	"k8s.io/kubernetes/pkg/util"
8 10
 
11
+	v1 "github.com/openshift/origin/pkg/api/v1"
9 12
 	deployapi "github.com/openshift/origin/pkg/deploy/api"
10
-	current "github.com/openshift/origin/pkg/deploy/api/v1"
11
-	kapi "k8s.io/kubernetes/pkg/api"
13
+	deployv1 "github.com/openshift/origin/pkg/deploy/api/v1"
12 14
 )
13 15
 
16
+func TestDefaults(t *testing.T) {
17
+	defaultIntOrString := util.NewIntOrStringFromString("25%")
18
+	differentIntOrString := util.NewIntOrStringFromInt(5)
19
+	tests := []struct {
20
+		original *deployv1.DeploymentConfig
21
+		expected *deployv1.DeploymentConfig
22
+	}{
23
+		{
24
+			original: &deployv1.DeploymentConfig{},
25
+			expected: &deployv1.DeploymentConfig{
26
+				Spec: deployv1.DeploymentConfigSpec{
27
+					Strategy: deployv1.DeploymentStrategy{
28
+						Type: deployv1.DeploymentStrategyTypeRolling,
29
+						RollingParams: &deployv1.RollingDeploymentStrategyParams{
30
+							UpdatePeriodSeconds: newInt64(deployapi.DefaultRollingUpdatePeriodSeconds),
31
+							IntervalSeconds:     newInt64(deployapi.DefaultRollingIntervalSeconds),
32
+							TimeoutSeconds:      newInt64(deployapi.DefaultRollingTimeoutSeconds),
33
+							MaxSurge:            &defaultIntOrString,
34
+							MaxUnavailable:      &defaultIntOrString,
35
+						},
36
+					},
37
+					Triggers: []deployv1.DeploymentTriggerPolicy{
38
+						{
39
+							Type: deployv1.DeploymentTriggerOnConfigChange,
40
+						},
41
+					},
42
+				},
43
+			},
44
+		},
45
+		{
46
+			original: &deployv1.DeploymentConfig{
47
+				Spec: deployv1.DeploymentConfigSpec{
48
+					Strategy: deployv1.DeploymentStrategy{
49
+						Type: deployv1.DeploymentStrategyTypeRecreate,
50
+						RollingParams: &deployv1.RollingDeploymentStrategyParams{
51
+							UpdatePeriodSeconds: newInt64(5),
52
+							IntervalSeconds:     newInt64(6),
53
+							TimeoutSeconds:      newInt64(7),
54
+							MaxSurge:            &differentIntOrString,
55
+							MaxUnavailable:      &differentIntOrString,
56
+						},
57
+					},
58
+					Triggers: []deployv1.DeploymentTriggerPolicy{
59
+						{
60
+							Type: deployv1.DeploymentTriggerOnImageChange,
61
+						},
62
+					},
63
+				},
64
+			},
65
+			expected: &deployv1.DeploymentConfig{
66
+				Spec: deployv1.DeploymentConfigSpec{
67
+					Strategy: deployv1.DeploymentStrategy{
68
+						Type: deployv1.DeploymentStrategyTypeRecreate,
69
+						RollingParams: &deployv1.RollingDeploymentStrategyParams{
70
+							UpdatePeriodSeconds: newInt64(5),
71
+							IntervalSeconds:     newInt64(6),
72
+							TimeoutSeconds:      newInt64(7),
73
+							MaxSurge:            &differentIntOrString,
74
+							MaxUnavailable:      &differentIntOrString,
75
+						},
76
+					},
77
+					Triggers: []deployv1.DeploymentTriggerPolicy{
78
+						{
79
+							Type: deployv1.DeploymentTriggerOnImageChange,
80
+						},
81
+					},
82
+				},
83
+			},
84
+		},
85
+		{
86
+			original: &deployv1.DeploymentConfig{
87
+				Spec: deployv1.DeploymentConfigSpec{
88
+					Strategy: deployv1.DeploymentStrategy{
89
+						Type: deployv1.DeploymentStrategyTypeRolling,
90
+						RollingParams: &deployv1.RollingDeploymentStrategyParams{
91
+							UpdatePeriodSeconds: newInt64(5),
92
+							IntervalSeconds:     newInt64(6),
93
+							TimeoutSeconds:      newInt64(7),
94
+							UpdatePercent:       newInt(50),
95
+						},
96
+					},
97
+					Triggers: []deployv1.DeploymentTriggerPolicy{
98
+						{
99
+							Type: deployv1.DeploymentTriggerOnImageChange,
100
+						},
101
+					},
102
+				},
103
+			},
104
+			expected: &deployv1.DeploymentConfig{
105
+				Spec: deployv1.DeploymentConfigSpec{
106
+					Strategy: deployv1.DeploymentStrategy{
107
+						Type: deployv1.DeploymentStrategyTypeRolling,
108
+						RollingParams: &deployv1.RollingDeploymentStrategyParams{
109
+							UpdatePeriodSeconds: newInt64(5),
110
+							IntervalSeconds:     newInt64(6),
111
+							TimeoutSeconds:      newInt64(7),
112
+							UpdatePercent:       newInt(50),
113
+							MaxSurge:            newIntOrString(util.NewIntOrStringFromString("50%")),
114
+							MaxUnavailable:      newIntOrString(util.NewIntOrStringFromInt(0)),
115
+						},
116
+					},
117
+					Triggers: []deployv1.DeploymentTriggerPolicy{
118
+						{
119
+							Type: deployv1.DeploymentTriggerOnImageChange,
120
+						},
121
+					},
122
+				},
123
+			},
124
+		},
125
+		{
126
+			original: &deployv1.DeploymentConfig{
127
+				Spec: deployv1.DeploymentConfigSpec{
128
+					Strategy: deployv1.DeploymentStrategy{
129
+						Type: deployv1.DeploymentStrategyTypeRolling,
130
+						RollingParams: &deployv1.RollingDeploymentStrategyParams{
131
+							UpdatePeriodSeconds: newInt64(5),
132
+							IntervalSeconds:     newInt64(6),
133
+							TimeoutSeconds:      newInt64(7),
134
+							UpdatePercent:       newInt(-25),
135
+						},
136
+					},
137
+					Triggers: []deployv1.DeploymentTriggerPolicy{
138
+						{
139
+							Type: deployv1.DeploymentTriggerOnImageChange,
140
+						},
141
+					},
142
+				},
143
+			},
144
+			expected: &deployv1.DeploymentConfig{
145
+				Spec: deployv1.DeploymentConfigSpec{
146
+					Strategy: deployv1.DeploymentStrategy{
147
+						Type: deployv1.DeploymentStrategyTypeRolling,
148
+						RollingParams: &deployv1.RollingDeploymentStrategyParams{
149
+							UpdatePeriodSeconds: newInt64(5),
150
+							IntervalSeconds:     newInt64(6),
151
+							TimeoutSeconds:      newInt64(7),
152
+							UpdatePercent:       newInt(-25),
153
+							MaxSurge:            newIntOrString(util.NewIntOrStringFromInt(0)),
154
+							MaxUnavailable:      newIntOrString(util.NewIntOrStringFromString("25%")),
155
+						},
156
+					},
157
+					Triggers: []deployv1.DeploymentTriggerPolicy{
158
+						{
159
+							Type: deployv1.DeploymentTriggerOnImageChange,
160
+						},
161
+					},
162
+				},
163
+			},
164
+		},
165
+	}
166
+
167
+	for i, test := range tests {
168
+		t.Logf("test %d", i)
169
+		original := test.original
170
+		expected := test.expected
171
+		obj2 := roundTrip(t, runtime.Object(original))
172
+		got, ok := obj2.(*deployv1.DeploymentConfig)
173
+		if !ok {
174
+			t.Errorf("unexpected object: %v", got)
175
+			t.FailNow()
176
+		}
177
+		if !reflect.DeepEqual(got.Spec, expected.Spec) {
178
+			t.Errorf("got different than expected:\nA:\t%#v\nB:\t%#v\n\nDiff:\n%s\n\n%s", got, expected, util.ObjectDiff(expected, got), util.ObjectGoPrintSideBySide(expected, got))
179
+		}
180
+	}
181
+}
182
+
14 183
 func roundTrip(t *testing.T, obj runtime.Object) runtime.Object {
15
-	data, err := kapi.Codec.Encode(obj)
184
+	data, err := v1.Codec.Encode(obj)
16 185
 	if err != nil {
17 186
 		t.Errorf("%v\n %#v", err, obj)
18 187
 		return nil
... ...
@@ -31,26 +200,14 @@ func roundTrip(t *testing.T, obj runtime.Object) runtime.Object {
31 31
 	return obj3
32 32
 }
33 33
 
34
-func TestDefaults(t *testing.T) {
35
-	c := &current.DeploymentConfig{}
36
-	o := roundTrip(t, runtime.Object(c))
37
-	config := o.(*current.DeploymentConfig)
34
+func newInt64(val int64) *int64 {
35
+	return &val
36
+}
38 37
 
39
-	if len(config.Spec.Triggers) != 1 && config.Spec.Triggers[0].Type != current.DeploymentTriggerOnConfigChange {
40
-		t.Errorf("expected default trigger for config: %#v", config.Spec.Triggers)
41
-	}
38
+func newInt(val int) *int {
39
+	return &val
40
+}
42 41
 
43
-	strat := config.Spec.Strategy
44
-	if e, a := current.DeploymentStrategyTypeRolling, strat.Type; e != a {
45
-		t.Errorf("expected strategy type %s, got %s", e, a)
46
-	}
47
-	if e, a := deployapi.DefaultRollingUpdatePeriodSeconds, *strat.RollingParams.UpdatePeriodSeconds; e != a {
48
-		t.Errorf("expected UpdatePeriodSeconds %d, got %d", e, a)
49
-	}
50
-	if e, a := deployapi.DefaultRollingIntervalSeconds, *strat.RollingParams.IntervalSeconds; e != a {
51
-		t.Errorf("expected IntervalSeconds %d, got %d", e, a)
52
-	}
53
-	if e, a := deployapi.DefaultRollingTimeoutSeconds, *strat.RollingParams.TimeoutSeconds; e != a {
54
-		t.Errorf("expected UpdatePeriodSeconds %d, got %d", e, a)
55
-	}
42
+func newIntOrString(ios util.IntOrString) *util.IntOrString {
43
+	return &ios
56 44
 }
... ...
@@ -2,6 +2,7 @@ package v1
2 2
 
3 3
 import (
4 4
 	kapi "k8s.io/kubernetes/pkg/api/v1"
5
+	kutil "k8s.io/kubernetes/pkg/util"
5 6
 )
6 7
 
7 8
 // DeploymentPhase describes the possible states a deployment can be in.
... ...
@@ -115,9 +116,36 @@ type RollingDeploymentStrategyParams struct {
115 115
 	// TimeoutSeconds is the time to wait for updates before giving up. If the
116 116
 	// value is nil, a default will be used.
117 117
 	TimeoutSeconds *int64 `json:"timeoutSeconds,omitempty" description:"the time to wait for updates before giving up"`
118
+	// MaxUnavailable is the maximum number of pods that can be unavailable
119
+	// during the update. Value can be an absolute number (ex: 5) or a
120
+	// percentage of total pods at the start of update (ex: 10%). Absolute
121
+	// number is calculated from percentage by rounding up.
122
+	//
123
+	// This cannot be 0 if MaxSurge is 0. By default, 25% is used.
124
+	//
125
+	// Example: when this is set to 30%, the old RC can be scaled down by 30%
126
+	// immediately when the rolling update starts. Once new pods are ready, old
127
+	// RC can be scaled down further, followed by scaling up the new RC,
128
+	// ensuring that at least 70% of original number of pods are available at
129
+	// all times during the update.
130
+	MaxUnavailable *kutil.IntOrString `json:"maxUnavailable,omitempty" description:"max number of pods that can be unavailable during the update; value can be an absolute number or a percentage of total pods at start of update"`
131
+	// MaxSurge is the maximum number of pods that can be scheduled above the
132
+	// original number of pods. Value can be an absolute number (ex: 5) or a
133
+	// percentage of total pods at the start of the update (ex: 10%). Absolute
134
+	// number is calculated from percentage by rounding up.
135
+	//
136
+	// This cannot be 0 if MaxUnavailable is 0. By default, 25% is used.
137
+	//
138
+	// Example: when this is set to 30%, the new RC can be scaled up by 30%
139
+	// immediately when the rolling update starts. Once old pods have been
140
+	// killed, new RC can be scaled up further, ensuring that total number of
141
+	// pods running at any time during the update is atmost 130% of original
142
+	// pods.
143
+	MaxSurge *kutil.IntOrString `json:"maxSurge,omitempty" description:"max number of pods that can be scheduled above the original number of pods; value can be an absolute number or a percentage of total pods at start of update"`
118 144
 	// UpdatePercent is the percentage of replicas to scale up or down each
119 145
 	// interval. If nil, one replica will be scaled up and down each interval.
120 146
 	// If negative, the scale order will be down/up instead of up/down.
147
+	// DEPRECATED: Use MaxUnavailable/MaxSurge instead.
121 148
 	UpdatePercent *int `json:"updatePercent,omitempty" description:"the percentage of replicas to scale up or down each interval (negative value switches scale order to down/up instead of up/down)"`
122 149
 	// Pre is a lifecycle hook which is executed before the deployment process
123 150
 	// begins. All LifecycleHookFailurePolicy values are supported.
... ...
@@ -2,9 +2,11 @@ package v1beta3
2 2
 
3 3
 import (
4 4
 	"fmt"
5
+	"math"
5 6
 
6 7
 	"k8s.io/kubernetes/pkg/api"
7 8
 	"k8s.io/kubernetes/pkg/conversion"
9
+	kutil "k8s.io/kubernetes/pkg/util"
8 10
 
9 11
 	newer "github.com/openshift/origin/pkg/deploy/api"
10 12
 	imageapi "github.com/openshift/origin/pkg/image/api"
... ...
@@ -195,6 +197,61 @@ func convert_api_DeploymentCauseImageTrigger_To_v1beta3_DeploymentCauseImageTrig
195 195
 	}
196 196
 	return nil
197 197
 }
198
+
199
+func convert_v1beta3_RollingDeploymentStrategyParams_To_api_RollingDeploymentStrategyParams(in *RollingDeploymentStrategyParams, out *newer.RollingDeploymentStrategyParams, s conversion.Scope) error {
200
+	out.UpdatePeriodSeconds = in.UpdatePeriodSeconds
201
+	out.IntervalSeconds = in.IntervalSeconds
202
+	out.TimeoutSeconds = in.TimeoutSeconds
203
+	out.UpdatePercent = in.UpdatePercent
204
+
205
+	if in.UpdatePercent != nil {
206
+		pct := kutil.NewIntOrStringFromString(fmt.Sprintf("%d%%", int(math.Abs(float64(*in.UpdatePercent)))))
207
+		if *in.UpdatePercent > 0 {
208
+			out.MaxSurge = pct
209
+		} else {
210
+			out.MaxUnavailable = pct
211
+		}
212
+	} else {
213
+		if err := s.Convert(in.MaxUnavailable, &out.MaxUnavailable, 0); err != nil {
214
+			return err
215
+		}
216
+		if err := s.Convert(in.MaxSurge, &out.MaxSurge, 0); err != nil {
217
+			return err
218
+		}
219
+	}
220
+	return nil
221
+}
222
+
223
+func convert_api_RollingDeploymentStrategyParams_To_v1beta3_RollingDeploymentStrategyParams(in *newer.RollingDeploymentStrategyParams, out *RollingDeploymentStrategyParams, s conversion.Scope) error {
224
+	out.UpdatePeriodSeconds = in.UpdatePeriodSeconds
225
+	out.IntervalSeconds = in.IntervalSeconds
226
+	out.TimeoutSeconds = in.TimeoutSeconds
227
+	out.UpdatePercent = in.UpdatePercent
228
+
229
+	if out.MaxUnavailable == nil {
230
+		out.MaxUnavailable = &kutil.IntOrString{}
231
+	}
232
+	if out.MaxSurge == nil {
233
+		out.MaxSurge = &kutil.IntOrString{}
234
+	}
235
+	if in.UpdatePercent != nil {
236
+		pct := kutil.NewIntOrStringFromString(fmt.Sprintf("%d%%", int(math.Abs(float64(*in.UpdatePercent)))))
237
+		if *in.UpdatePercent > 0 {
238
+			out.MaxSurge = &pct
239
+		} else {
240
+			out.MaxUnavailable = &pct
241
+		}
242
+	} else {
243
+		if err := s.Convert(&in.MaxUnavailable, out.MaxUnavailable, 0); err != nil {
244
+			return err
245
+		}
246
+		if err := s.Convert(&in.MaxSurge, out.MaxSurge, 0); err != nil {
247
+			return err
248
+		}
249
+	}
250
+	return nil
251
+}
252
+
198 253
 func init() {
199 254
 	err := api.Scheme.AddDefaultingFuncs(
200 255
 		func(obj *DeploymentTriggerImageChangeParams) {
... ...
@@ -218,6 +275,8 @@ func init() {
218 218
 		convert_api_DeploymentTriggerImageChangeParams_To_v1beta3_DeploymentTriggerImageChangeParams,
219 219
 		convert_v1beta3_DeploymentCauseImageTrigger_To_api_DeploymentCauseImageTrigger,
220 220
 		convert_api_DeploymentCauseImageTrigger_To_v1beta3_DeploymentCauseImageTrigger,
221
+		convert_v1beta3_RollingDeploymentStrategyParams_To_api_RollingDeploymentStrategyParams,
222
+		convert_api_RollingDeploymentStrategyParams_To_v1beta3_RollingDeploymentStrategyParams,
221 223
 	)
222 224
 	if err != nil {
223 225
 		panic(err)
... ...
@@ -1,31 +1,181 @@
1 1
 package v1beta3
2 2
 
3 3
 import (
4
+	"reflect"
4 5
 	"testing"
5 6
 
6
-	"k8s.io/kubernetes/pkg/api"
7
-	kapi "k8s.io/kubernetes/pkg/api/v1beta3"
7
+	kapi "k8s.io/kubernetes/pkg/api"
8
+	kapiv1beta3 "k8s.io/kubernetes/pkg/api/v1beta3"
9
+	"k8s.io/kubernetes/pkg/util"
8 10
 
9 11
 	newer "github.com/openshift/origin/pkg/deploy/api"
10 12
 )
11 13
 
12 14
 func TestTriggerRoundTrip(t *testing.T) {
13 15
 	p := DeploymentTriggerImageChangeParams{
14
-		From: kapi.ObjectReference{
16
+		From: kapiv1beta3.ObjectReference{
15 17
 			Kind: "DockerImage",
16 18
 			Name: "",
17 19
 		},
18 20
 	}
19 21
 	out := &newer.DeploymentTriggerImageChangeParams{}
20
-	if err := api.Scheme.Convert(&p, out); err == nil {
22
+	if err := kapi.Scheme.Convert(&p, out); err == nil {
21 23
 		t.Errorf("unexpected error: %v", err)
22 24
 	}
23 25
 	p.From.Name = "a/b:test"
24 26
 	out = &newer.DeploymentTriggerImageChangeParams{}
25
-	if err := api.Scheme.Convert(&p, out); err != nil {
27
+	if err := kapi.Scheme.Convert(&p, out); err != nil {
26 28
 		t.Errorf("unexpected error: %v", err)
27 29
 	}
28 30
 	if out.RepositoryName != "a/b" && out.Tag != "test" {
29 31
 		t.Errorf("unexpected output: %#v", out)
30 32
 	}
31 33
 }
34
+
35
+func Test_convert_v1beta3_RollingDeploymentStrategyParams_To_api_RollingDeploymentStrategyParams(t *testing.T) {
36
+	tests := []struct {
37
+		in  *RollingDeploymentStrategyParams
38
+		out *newer.RollingDeploymentStrategyParams
39
+	}{
40
+		{
41
+			in: &RollingDeploymentStrategyParams{
42
+				UpdatePeriodSeconds: newInt64(5),
43
+				IntervalSeconds:     newInt64(6),
44
+				TimeoutSeconds:      newInt64(7),
45
+				UpdatePercent:       newInt(-25),
46
+			},
47
+			out: &newer.RollingDeploymentStrategyParams{
48
+				UpdatePeriodSeconds: newInt64(5),
49
+				IntervalSeconds:     newInt64(6),
50
+				TimeoutSeconds:      newInt64(7),
51
+				UpdatePercent:       newInt(-25),
52
+				MaxSurge:            util.NewIntOrStringFromInt(0),
53
+				MaxUnavailable:      util.NewIntOrStringFromString("25%"),
54
+			},
55
+		},
56
+		{
57
+			in: &RollingDeploymentStrategyParams{
58
+				UpdatePeriodSeconds: newInt64(5),
59
+				IntervalSeconds:     newInt64(6),
60
+				TimeoutSeconds:      newInt64(7),
61
+				UpdatePercent:       newInt(25),
62
+			},
63
+			out: &newer.RollingDeploymentStrategyParams{
64
+				UpdatePeriodSeconds: newInt64(5),
65
+				IntervalSeconds:     newInt64(6),
66
+				TimeoutSeconds:      newInt64(7),
67
+				UpdatePercent:       newInt(25),
68
+				MaxSurge:            util.NewIntOrStringFromString("25%"),
69
+				MaxUnavailable:      util.NewIntOrStringFromInt(0),
70
+			},
71
+		},
72
+		{
73
+			in: &RollingDeploymentStrategyParams{
74
+				UpdatePeriodSeconds: newInt64(5),
75
+				IntervalSeconds:     newInt64(6),
76
+				TimeoutSeconds:      newInt64(7),
77
+				MaxSurge:            newIntOrString(util.NewIntOrStringFromInt(10)),
78
+				MaxUnavailable:      newIntOrString(util.NewIntOrStringFromInt(20)),
79
+			},
80
+			out: &newer.RollingDeploymentStrategyParams{
81
+				UpdatePeriodSeconds: newInt64(5),
82
+				IntervalSeconds:     newInt64(6),
83
+				TimeoutSeconds:      newInt64(7),
84
+				MaxSurge:            util.NewIntOrStringFromInt(10),
85
+				MaxUnavailable:      util.NewIntOrStringFromInt(20),
86
+			},
87
+		},
88
+	}
89
+
90
+	for _, test := range tests {
91
+		out := &newer.RollingDeploymentStrategyParams{}
92
+		if err := kapi.Scheme.Convert(test.in, out); err != nil {
93
+			t.Errorf("unexpected error: %v", err)
94
+		}
95
+		if !reflect.DeepEqual(out, test.out) {
96
+			t.Errorf("got different than expected:\nA:\t%#v\nB:\t%#v\n\nDiff:\n%s\n\n%s", out, test.out, util.ObjectDiff(test.out, out), util.ObjectGoPrintSideBySide(test.out, out))
97
+		}
98
+	}
99
+}
100
+
101
+func Test_convert_api_RollingDeploymentStrategyParams_To_v1beta3_RollingDeploymentStrategyParams(t *testing.T) {
102
+	tests := []struct {
103
+		in  *newer.RollingDeploymentStrategyParams
104
+		out *RollingDeploymentStrategyParams
105
+	}{
106
+		{
107
+			in: &newer.RollingDeploymentStrategyParams{
108
+				UpdatePeriodSeconds: newInt64(5),
109
+				IntervalSeconds:     newInt64(6),
110
+				TimeoutSeconds:      newInt64(7),
111
+				UpdatePercent:       newInt(-25),
112
+				MaxSurge:            util.NewIntOrStringFromInt(0),
113
+				MaxUnavailable:      util.NewIntOrStringFromString("25%"),
114
+			},
115
+			out: &RollingDeploymentStrategyParams{
116
+				UpdatePeriodSeconds: newInt64(5),
117
+				IntervalSeconds:     newInt64(6),
118
+				TimeoutSeconds:      newInt64(7),
119
+				UpdatePercent:       newInt(-25),
120
+				MaxSurge:            newIntOrString(util.NewIntOrStringFromInt(0)),
121
+				MaxUnavailable:      newIntOrString(util.NewIntOrStringFromString("25%")),
122
+			},
123
+		},
124
+		{
125
+			in: &newer.RollingDeploymentStrategyParams{
126
+				UpdatePeriodSeconds: newInt64(5),
127
+				IntervalSeconds:     newInt64(6),
128
+				TimeoutSeconds:      newInt64(7),
129
+				UpdatePercent:       newInt(25),
130
+				MaxSurge:            util.NewIntOrStringFromString("25%"),
131
+				MaxUnavailable:      util.NewIntOrStringFromInt(0),
132
+			},
133
+			out: &RollingDeploymentStrategyParams{
134
+				UpdatePeriodSeconds: newInt64(5),
135
+				IntervalSeconds:     newInt64(6),
136
+				TimeoutSeconds:      newInt64(7),
137
+				UpdatePercent:       newInt(25),
138
+				MaxSurge:            newIntOrString(util.NewIntOrStringFromString("25%")),
139
+				MaxUnavailable:      newIntOrString(util.NewIntOrStringFromInt(0)),
140
+			},
141
+		},
142
+		{
143
+			in: &newer.RollingDeploymentStrategyParams{
144
+				UpdatePeriodSeconds: newInt64(5),
145
+				IntervalSeconds:     newInt64(6),
146
+				TimeoutSeconds:      newInt64(7),
147
+				MaxSurge:            util.NewIntOrStringFromInt(10),
148
+				MaxUnavailable:      util.NewIntOrStringFromInt(20),
149
+			},
150
+			out: &RollingDeploymentStrategyParams{
151
+				UpdatePeriodSeconds: newInt64(5),
152
+				IntervalSeconds:     newInt64(6),
153
+				TimeoutSeconds:      newInt64(7),
154
+				MaxSurge:            newIntOrString(util.NewIntOrStringFromInt(10)),
155
+				MaxUnavailable:      newIntOrString(util.NewIntOrStringFromInt(20)),
156
+			},
157
+		},
158
+	}
159
+
160
+	for _, test := range tests {
161
+		out := &RollingDeploymentStrategyParams{}
162
+		if err := kapi.Scheme.Convert(test.in, out); err != nil {
163
+			t.Errorf("unexpected error: %v", err)
164
+		}
165
+		if !reflect.DeepEqual(out, test.out) {
166
+			t.Errorf("got different than expected:\nA:\t%#v\nB:\t%#v\n\nDiff:\n%s\n\n%s", out, test.out, util.ObjectDiff(test.out, out), util.ObjectGoPrintSideBySide(test.out, out))
167
+		}
168
+	}
169
+}
170
+
171
+func newInt64(val int64) *int64 {
172
+	return &val
173
+}
174
+
175
+func newInt(val int) *int {
176
+	return &val
177
+}
178
+
179
+func newIntOrString(ios util.IntOrString) *util.IntOrString {
180
+	return &ios
181
+}
... ...
@@ -2,6 +2,7 @@ package v1beta3
2 2
 
3 3
 import (
4 4
 	"k8s.io/kubernetes/pkg/api"
5
+	kutil "k8s.io/kubernetes/pkg/util"
5 6
 
6 7
 	deployapi "github.com/openshift/origin/pkg/deploy/api"
7 8
 )
... ...
@@ -37,6 +38,18 @@ func init() {
37 37
 			if obj.TimeoutSeconds == nil {
38 38
 				obj.TimeoutSeconds = mkintp(deployapi.DefaultRollingTimeoutSeconds)
39 39
 			}
40
+
41
+			if obj.UpdatePercent == nil {
42
+				// Apply defaults.
43
+				if obj.MaxUnavailable == nil {
44
+					maxUnavailable := kutil.NewIntOrStringFromString("25%")
45
+					obj.MaxUnavailable = &maxUnavailable
46
+				}
47
+				if obj.MaxSurge == nil {
48
+					maxSurge := kutil.NewIntOrStringFromString("25%")
49
+					obj.MaxSurge = &maxSurge
50
+				}
51
+			}
40 52
 		},
41 53
 	)
42 54
 	if err != nil {
... ...
@@ -4,15 +4,180 @@ import (
4 4
 	"reflect"
5 5
 	"testing"
6 6
 
7
+	kapi "k8s.io/kubernetes/pkg/api"
7 8
 	"k8s.io/kubernetes/pkg/runtime"
9
+	"k8s.io/kubernetes/pkg/util"
8 10
 
11
+	v1 "github.com/openshift/origin/pkg/api/v1beta3"
9 12
 	deployapi "github.com/openshift/origin/pkg/deploy/api"
10
-	current "github.com/openshift/origin/pkg/deploy/api/v1beta3"
11
-	kapi "k8s.io/kubernetes/pkg/api"
13
+	deployv1 "github.com/openshift/origin/pkg/deploy/api/v1beta3"
12 14
 )
13 15
 
16
+func TestDefaults(t *testing.T) {
17
+	defaultIntOrString := util.NewIntOrStringFromString("25%")
18
+	differentIntOrString := util.NewIntOrStringFromInt(5)
19
+	tests := []struct {
20
+		original *deployv1.DeploymentConfig
21
+		expected *deployv1.DeploymentConfig
22
+	}{
23
+		{
24
+			original: &deployv1.DeploymentConfig{},
25
+			expected: &deployv1.DeploymentConfig{
26
+				Spec: deployv1.DeploymentConfigSpec{
27
+					Strategy: deployv1.DeploymentStrategy{
28
+						Type: deployv1.DeploymentStrategyTypeRolling,
29
+						RollingParams: &deployv1.RollingDeploymentStrategyParams{
30
+							UpdatePeriodSeconds: newInt64(deployapi.DefaultRollingUpdatePeriodSeconds),
31
+							IntervalSeconds:     newInt64(deployapi.DefaultRollingIntervalSeconds),
32
+							TimeoutSeconds:      newInt64(deployapi.DefaultRollingTimeoutSeconds),
33
+							MaxSurge:            &defaultIntOrString,
34
+							MaxUnavailable:      &defaultIntOrString,
35
+						},
36
+					},
37
+				},
38
+			},
39
+		},
40
+		{
41
+			original: &deployv1.DeploymentConfig{
42
+				Spec: deployv1.DeploymentConfigSpec{
43
+					Strategy: deployv1.DeploymentStrategy{
44
+						Type: deployv1.DeploymentStrategyTypeRecreate,
45
+						RollingParams: &deployv1.RollingDeploymentStrategyParams{
46
+							UpdatePeriodSeconds: newInt64(5),
47
+							IntervalSeconds:     newInt64(6),
48
+							TimeoutSeconds:      newInt64(7),
49
+							MaxSurge:            &differentIntOrString,
50
+							MaxUnavailable:      &differentIntOrString,
51
+						},
52
+					},
53
+					Triggers: []deployv1.DeploymentTriggerPolicy{
54
+						{
55
+							Type: deployv1.DeploymentTriggerOnImageChange,
56
+						},
57
+					},
58
+				},
59
+			},
60
+			expected: &deployv1.DeploymentConfig{
61
+				Spec: deployv1.DeploymentConfigSpec{
62
+					Strategy: deployv1.DeploymentStrategy{
63
+						Type: deployv1.DeploymentStrategyTypeRecreate,
64
+						RollingParams: &deployv1.RollingDeploymentStrategyParams{
65
+							UpdatePeriodSeconds: newInt64(5),
66
+							IntervalSeconds:     newInt64(6),
67
+							TimeoutSeconds:      newInt64(7),
68
+							MaxSurge:            &differentIntOrString,
69
+							MaxUnavailable:      &differentIntOrString,
70
+						},
71
+					},
72
+					Triggers: []deployv1.DeploymentTriggerPolicy{
73
+						{
74
+							Type: deployv1.DeploymentTriggerOnImageChange,
75
+						},
76
+					},
77
+				},
78
+			},
79
+		},
80
+		{
81
+			original: &deployv1.DeploymentConfig{
82
+				Spec: deployv1.DeploymentConfigSpec{
83
+					Strategy: deployv1.DeploymentStrategy{
84
+						Type: deployv1.DeploymentStrategyTypeRolling,
85
+						RollingParams: &deployv1.RollingDeploymentStrategyParams{
86
+							UpdatePeriodSeconds: newInt64(5),
87
+							IntervalSeconds:     newInt64(6),
88
+							TimeoutSeconds:      newInt64(7),
89
+							UpdatePercent:       newInt(50),
90
+						},
91
+					},
92
+					Triggers: []deployv1.DeploymentTriggerPolicy{
93
+						{
94
+							Type: deployv1.DeploymentTriggerOnImageChange,
95
+						},
96
+					},
97
+				},
98
+			},
99
+			expected: &deployv1.DeploymentConfig{
100
+				Spec: deployv1.DeploymentConfigSpec{
101
+					Strategy: deployv1.DeploymentStrategy{
102
+						Type: deployv1.DeploymentStrategyTypeRolling,
103
+						RollingParams: &deployv1.RollingDeploymentStrategyParams{
104
+							UpdatePeriodSeconds: newInt64(5),
105
+							IntervalSeconds:     newInt64(6),
106
+							TimeoutSeconds:      newInt64(7),
107
+							UpdatePercent:       newInt(50),
108
+							MaxSurge:            newIntOrString(util.NewIntOrStringFromString("50%")),
109
+							MaxUnavailable:      newIntOrString(util.NewIntOrStringFromInt(0)),
110
+						},
111
+					},
112
+					Triggers: []deployv1.DeploymentTriggerPolicy{
113
+						{
114
+							Type: deployv1.DeploymentTriggerOnImageChange,
115
+						},
116
+					},
117
+				},
118
+			},
119
+		},
120
+		{
121
+			original: &deployv1.DeploymentConfig{
122
+				Spec: deployv1.DeploymentConfigSpec{
123
+					Strategy: deployv1.DeploymentStrategy{
124
+						Type: deployv1.DeploymentStrategyTypeRolling,
125
+						RollingParams: &deployv1.RollingDeploymentStrategyParams{
126
+							UpdatePeriodSeconds: newInt64(5),
127
+							IntervalSeconds:     newInt64(6),
128
+							TimeoutSeconds:      newInt64(7),
129
+							UpdatePercent:       newInt(-50),
130
+						},
131
+					},
132
+					Triggers: []deployv1.DeploymentTriggerPolicy{
133
+						{
134
+							Type: deployv1.DeploymentTriggerOnImageChange,
135
+						},
136
+					},
137
+				},
138
+			},
139
+			expected: &deployv1.DeploymentConfig{
140
+				Spec: deployv1.DeploymentConfigSpec{
141
+					Strategy: deployv1.DeploymentStrategy{
142
+						Type: deployv1.DeploymentStrategyTypeRolling,
143
+						RollingParams: &deployv1.RollingDeploymentStrategyParams{
144
+							UpdatePeriodSeconds: newInt64(5),
145
+							IntervalSeconds:     newInt64(6),
146
+							TimeoutSeconds:      newInt64(7),
147
+							UpdatePercent:       newInt(-50),
148
+							MaxSurge:            newIntOrString(util.NewIntOrStringFromInt(0)),
149
+							MaxUnavailable:      newIntOrString(util.NewIntOrStringFromString("50%")),
150
+						},
151
+					},
152
+					Triggers: []deployv1.DeploymentTriggerPolicy{
153
+						{
154
+							Type: deployv1.DeploymentTriggerOnImageChange,
155
+						},
156
+					},
157
+				},
158
+			},
159
+		},
160
+	}
161
+
162
+	for i, test := range tests {
163
+		t.Logf("test %d", i)
164
+		original := test.original
165
+		expected := test.expected
166
+		obj2 := roundTrip(t, runtime.Object(original))
167
+		got, ok := obj2.(*deployv1.DeploymentConfig)
168
+		if !ok {
169
+			t.Errorf("unexpected object: %v", got)
170
+			t.FailNow()
171
+		}
172
+		if !reflect.DeepEqual(got.Spec, expected.Spec) {
173
+			t.Errorf("got different than expected:\nA:\t%#v\nB:\t%#v\n\nDiff:\n%s\n\n%s", got, expected, util.ObjectDiff(expected, got), util.ObjectGoPrintSideBySide(expected, got))
174
+
175
+		}
176
+	}
177
+}
178
+
14 179
 func roundTrip(t *testing.T, obj runtime.Object) runtime.Object {
15
-	data, err := kapi.Codec.Encode(obj)
180
+	data, err := v1.Codec.Encode(obj)
16 181
 	if err != nil {
17 182
 		t.Errorf("%v\n %#v", err, obj)
18 183
 		return nil
... ...
@@ -31,21 +196,14 @@ func roundTrip(t *testing.T, obj runtime.Object) runtime.Object {
31 31
 	return obj3
32 32
 }
33 33
 
34
-func TestDefaults_rollingParams(t *testing.T) {
35
-	c := &current.DeploymentConfig{}
36
-	o := roundTrip(t, runtime.Object(c))
37
-	config := o.(*current.DeploymentConfig)
38
-	strat := config.Spec.Strategy
39
-	if e, a := current.DeploymentStrategyTypeRolling, strat.Type; e != a {
40
-		t.Errorf("expected strategy type %s, got %s", e, a)
41
-	}
42
-	if e, a := deployapi.DefaultRollingUpdatePeriodSeconds, *strat.RollingParams.UpdatePeriodSeconds; e != a {
43
-		t.Errorf("expected UpdatePeriodSeconds %d, got %d", e, a)
44
-	}
45
-	if e, a := deployapi.DefaultRollingIntervalSeconds, *strat.RollingParams.IntervalSeconds; e != a {
46
-		t.Errorf("expected IntervalSeconds %d, got %d", e, a)
47
-	}
48
-	if e, a := deployapi.DefaultRollingTimeoutSeconds, *strat.RollingParams.TimeoutSeconds; e != a {
49
-		t.Errorf("expected UpdatePeriodSeconds %d, got %d", e, a)
50
-	}
34
+func newInt64(val int64) *int64 {
35
+	return &val
36
+}
37
+
38
+func newInt(val int) *int {
39
+	return &val
40
+}
41
+
42
+func newIntOrString(ios util.IntOrString) *util.IntOrString {
43
+	return &ios
51 44
 }
... ...
@@ -2,6 +2,7 @@ package v1beta3
2 2
 
3 3
 import (
4 4
 	kapi "k8s.io/kubernetes/pkg/api/v1beta3"
5
+	kutil "k8s.io/kubernetes/pkg/util"
5 6
 )
6 7
 
7 8
 // DeploymentPhase describes the possible states a deployment can be in.
... ...
@@ -115,9 +116,36 @@ type RollingDeploymentStrategyParams struct {
115 115
 	// TimeoutSeconds is the time to wait for updates before giving up. If the
116 116
 	// value is nil, a default will be used.
117 117
 	TimeoutSeconds *int64 `json:"timeoutSeconds,omitempty" description:"the time to wait for updates before giving up"`
118
+	// MaxUnavailable is the maximum number of pods that can be unavailable
119
+	// during the update. Value can be an absolute number (ex: 5) or a
120
+	// percentage of total pods at the start of update (ex: 10%). Absolute
121
+	// number is calculated from percentage by rounding up.
122
+	//
123
+	// This cannot be 0 if MaxSurge is 0. By default, 25% is used.
124
+	//
125
+	// Example: when this is set to 30%, the old RC can be scaled down by 30%
126
+	// immediately when the rolling update starts. Once new pods are ready, old
127
+	// RC can be scaled down further, followed by scaling up the new RC,
128
+	// ensuring that at least 70% of original number of pods are available at
129
+	// all times during the update.
130
+	MaxUnavailable *kutil.IntOrString `json:"maxUnavailable,omitempty" description:"max number of pods that can be unavailable during the update; value can be an absolute number or a percentage of total pods at start of update"`
131
+	// MaxSurge is the maximum number of pods that can be scheduled above the
132
+	// original number of pods. Value can be an absolute number (ex: 5) or a
133
+	// percentage of total pods at the start of the update (ex: 10%). Absolute
134
+	// number is calculated from percentage by rounding up.
135
+	//
136
+	// This cannot be 0 if MaxUnavailable is 0. By default, 25% is used.
137
+	//
138
+	// Example: when this is set to 30%, the new RC can be scaled up by 30%
139
+	// immediately when the rolling update starts. Once old pods have been
140
+	// killed, new RC can be scaled up further, ensuring that total number of
141
+	// pods running at any time during the update is atmost 130% of original
142
+	// pods.
143
+	MaxSurge *kutil.IntOrString `json:"maxSurge,omitempty" description:"max number of pods that can be scheduled above the original number of pods; value can be an absolute number or a percentage of total pods at start of update"`
118 144
 	// UpdatePercent is the percentage of replicas to scale up or down each
119 145
 	// interval. If nil, one replica will be scaled up and down each interval.
120 146
 	// If negative, the scale order will be down/up instead of up/down.
147
+	// DEPRECATED: Use MaxUnavailable/MaxSurge instead.
121 148
 	UpdatePercent *int `json:"updatePercent,omitempty" description:"the percentage of replicas to scale up or down each interval (negative value switches scale order to down/up instead of up/down)"`
122 149
 	// Pre is a lifecycle hook which is executed before the deployment process
123 150
 	// begins. All LifecycleHookFailurePolicy values are supported.
... ...
@@ -2,6 +2,8 @@ package validation
2 2
 
3 3
 import (
4 4
 	"fmt"
5
+	"regexp"
6
+	"strconv"
5 7
 	"strings"
6 8
 
7 9
 	kapi "k8s.io/kubernetes/pkg/api"
... ...
@@ -186,6 +188,17 @@ func validateRollingParams(params *deployapi.RollingDeploymentStrategyParams) fi
186 186
 			errs = append(errs, fielderrors.NewFieldInvalid("updatePercent", *params.UpdatePercent, "must be between 1 and 100 or between -1 and -100 (inclusive)"))
187 187
 		}
188 188
 	}
189
+	// Most of this is lifted from the upstream experimental deployments API. We
190
+	// can't reuse it directly yet, but no use reinventing the logic, so copy-
191
+	// pasted and adapted here.
192
+	errs = append(errs, ValidatePositiveIntOrPercent(params.MaxUnavailable, "maxUnavailable")...)
193
+	errs = append(errs, ValidatePositiveIntOrPercent(params.MaxSurge, "maxSurge")...)
194
+	if getIntOrPercentValue(params.MaxUnavailable) == 0 && getIntOrPercentValue(params.MaxSurge) == 0 {
195
+		// Both MaxSurge and MaxUnavailable cannot be zero.
196
+		errs = append(errs, fielderrors.NewFieldInvalid("maxUnavailable", params.MaxUnavailable, "cannot be 0 when maxSurge is 0 as well"))
197
+	}
198
+	// Validate that MaxUnavailable is not more than 100%.
199
+	errs = append(errs, IsNotMoreThan100Percent(params.MaxUnavailable, "maxUnavailable")...)
189 200
 
190 201
 	if params.Pre != nil {
191 202
 		errs = append(errs, validateLifecycleHook(params.Pre).Prefix("pre")...)
... ...
@@ -250,3 +263,60 @@ func validateImageChangeParams(params *deployapi.DeploymentTriggerImageChangePar
250 250
 
251 251
 	return errs
252 252
 }
253
+
254
+func ValidatePositiveIntOrPercent(intOrPercent util.IntOrString, fieldName string) fielderrors.ValidationErrorList {
255
+	allErrs := fielderrors.ValidationErrorList{}
256
+	if intOrPercent.Kind == util.IntstrString {
257
+		if !IsValidPercent(intOrPercent.StrVal) {
258
+			allErrs = append(allErrs, fielderrors.NewFieldInvalid(fieldName, intOrPercent, "value should be int(5) or percentage(5%)"))
259
+		}
260
+
261
+	} else if intOrPercent.Kind == util.IntstrInt {
262
+		allErrs = append(allErrs, ValidatePositiveField(int64(intOrPercent.IntVal), fieldName)...)
263
+	}
264
+	return allErrs
265
+}
266
+
267
+func getPercentValue(intOrStringValue util.IntOrString) (int, bool) {
268
+	if intOrStringValue.Kind != util.IntstrString || !IsValidPercent(intOrStringValue.StrVal) {
269
+		return 0, false
270
+	}
271
+	value, _ := strconv.Atoi(intOrStringValue.StrVal[:len(intOrStringValue.StrVal)-1])
272
+	return value, true
273
+}
274
+
275
+func getIntOrPercentValue(intOrStringValue util.IntOrString) int {
276
+	value, isPercent := getPercentValue(intOrStringValue)
277
+	if isPercent {
278
+		return value
279
+	}
280
+	return intOrStringValue.IntVal
281
+}
282
+
283
+func IsNotMoreThan100Percent(intOrStringValue util.IntOrString, fieldName string) fielderrors.ValidationErrorList {
284
+	allErrs := fielderrors.ValidationErrorList{}
285
+	value, isPercent := getPercentValue(intOrStringValue)
286
+	if !isPercent || value <= 100 {
287
+		return nil
288
+	}
289
+	allErrs = append(allErrs, fielderrors.NewFieldInvalid(fieldName, intOrStringValue, "should not be more than 100%"))
290
+	return allErrs
291
+}
292
+
293
+func ValidatePositiveField(value int64, fieldName string) fielderrors.ValidationErrorList {
294
+	allErrs := fielderrors.ValidationErrorList{}
295
+	if value < 0 {
296
+		allErrs = append(allErrs, fielderrors.NewFieldInvalid(fieldName, value, isNegativeErrorMsg))
297
+	}
298
+	return allErrs
299
+}
300
+
301
+const percentFmt string = "[0-9]+%"
302
+
303
+var percentRegexp = regexp.MustCompile("^" + percentFmt + "$")
304
+
305
+func IsValidPercent(percent string) bool {
306
+	return percentRegexp.MatchString(percent)
307
+}
308
+
309
+const isNegativeErrorMsg string = `must be non-negative`
... ...
@@ -4,6 +4,7 @@ import (
4 4
 	"testing"
5 5
 
6 6
 	kapi "k8s.io/kubernetes/pkg/api"
7
+	kutil "k8s.io/kubernetes/pkg/util"
7 8
 	"k8s.io/kubernetes/pkg/util/fielderrors"
8 9
 
9 10
 	"github.com/openshift/origin/pkg/deploy/api"
... ...
@@ -31,6 +32,7 @@ func rollingConfig(interval, updatePeriod, timeout int) api.DeploymentConfig {
31 31
 					IntervalSeconds:     mkint64p(interval),
32 32
 					UpdatePeriodSeconds: mkint64p(updatePeriod),
33 33
 					TimeoutSeconds:      mkint64p(timeout),
34
+					MaxSurge:            kutil.NewIntOrStringFromInt(1),
34 35
 				},
35 36
 			},
36 37
 			ControllerTemplate: test.OkControllerTemplate(),
... ...
@@ -38,7 +40,7 @@ func rollingConfig(interval, updatePeriod, timeout int) api.DeploymentConfig {
38 38
 	}
39 39
 }
40 40
 
41
-func rollingConfigPct(interval, updatePeriod, timeout, percent int) api.DeploymentConfig {
41
+func rollingConfigMax(maxSurge, maxUnavailable kutil.IntOrString) api.DeploymentConfig {
42 42
 	return api.DeploymentConfig{
43 43
 		ObjectMeta: kapi.ObjectMeta{Name: "foo", Namespace: "bar"},
44 44
 		Triggers:   manualTrigger(),
... ...
@@ -46,10 +48,11 @@ func rollingConfigPct(interval, updatePeriod, timeout, percent int) api.Deployme
46 46
 			Strategy: api.DeploymentStrategy{
47 47
 				Type: api.DeploymentStrategyTypeRolling,
48 48
 				RollingParams: &api.RollingDeploymentStrategyParams{
49
-					IntervalSeconds:     mkint64p(interval),
50
-					UpdatePeriodSeconds: mkint64p(updatePeriod),
51
-					TimeoutSeconds:      mkint64p(timeout),
52
-					UpdatePercent:       mkintp(percent),
49
+					IntervalSeconds:     mkint64p(1),
50
+					UpdatePeriodSeconds: mkint64p(1),
51
+					TimeoutSeconds:      mkint64p(1),
52
+					MaxSurge:            maxSurge,
53
+					MaxUnavailable:      maxUnavailable,
53 54
 				},
54 55
 			},
55 56
 			ControllerTemplate: test.OkControllerTemplate(),
... ...
@@ -346,6 +349,7 @@ func TestValidateDeploymentConfigMissingFields(t *testing.T) {
346 346
 							IntervalSeconds:     mkint64p(1),
347 347
 							UpdatePeriodSeconds: mkint64p(1),
348 348
 							TimeoutSeconds:      mkint64p(20),
349
+							MaxSurge:            kutil.NewIntOrStringFromInt(1),
349 350
 							Pre: &api.LifecycleHook{
350 351
 								ExecNewPod: &api.ExecNewPodHook{
351 352
 									Command:       []string{"cmd"},
... ...
@@ -360,40 +364,45 @@ func TestValidateDeploymentConfigMissingFields(t *testing.T) {
360 360
 			fielderrors.ValidationErrorTypeRequired,
361 361
 			"template.strategy.rollingParams.pre.failurePolicy",
362 362
 		},
363
-		"invalid zero template.strategy.rollingParams.updatePercent": {
364
-			rollingConfigPct(1, 1, 1, 0),
363
+		"both maxSurge and maxUnavailable 0 template.strategy.rollingParams.maxUnavailable": {
364
+			rollingConfigMax(kutil.NewIntOrStringFromInt(0), kutil.NewIntOrStringFromInt(0)),
365
+			fielderrors.ValidationErrorTypeInvalid,
366
+			"template.strategy.rollingParams.maxUnavailable",
367
+		},
368
+		"invalid lower bound template.strategy.rollingParams.maxUnavailable": {
369
+			rollingConfigMax(kutil.NewIntOrStringFromInt(0), kutil.NewIntOrStringFromInt(-100)),
370
+			fielderrors.ValidationErrorTypeInvalid,
371
+			"template.strategy.rollingParams.maxUnavailable",
372
+		},
373
+		"invalid lower bound template.strategy.rollingParams.maxSurge": {
374
+			rollingConfigMax(kutil.NewIntOrStringFromInt(-1), kutil.NewIntOrStringFromInt(0)),
375
+			fielderrors.ValidationErrorTypeInvalid,
376
+			"template.strategy.rollingParams.maxSurge",
377
+		},
378
+		"both maxSurge and maxUnavailable 0 percent template.strategy.rollingParams.maxUnavailable": {
379
+			rollingConfigMax(kutil.NewIntOrStringFromString("0%"), kutil.NewIntOrStringFromString("0%")),
380
+			fielderrors.ValidationErrorTypeInvalid,
381
+			"template.strategy.rollingParams.maxUnavailable",
382
+		},
383
+		"invalid lower bound percent template.strategy.rollingParams.maxUnavailable": {
384
+			rollingConfigMax(kutil.NewIntOrStringFromInt(0), kutil.NewIntOrStringFromString("-1%")),
385
+			fielderrors.ValidationErrorTypeInvalid,
386
+			"template.strategy.rollingParams.maxUnavailable",
387
+		},
388
+		"invalid upper bound percent template.strategy.rollingParams.maxUnavailable": {
389
+			rollingConfigMax(kutil.NewIntOrStringFromInt(0), kutil.NewIntOrStringFromString("101%")),
365 390
 			fielderrors.ValidationErrorTypeInvalid,
366
-			"template.strategy.rollingParams.updatePercent",
391
+			"template.strategy.rollingParams.maxUnavailable",
367 392
 		},
368
-		"invalid lower bound template.strategy.rollingParams.updatePercent": {
369
-			rollingConfigPct(1, 1, 1, -101),
393
+		"invalid percent template.strategy.rollingParams.maxUnavailable": {
394
+			rollingConfigMax(kutil.NewIntOrStringFromInt(0), kutil.NewIntOrStringFromString("foo")),
370 395
 			fielderrors.ValidationErrorTypeInvalid,
371
-			"template.strategy.rollingParams.updatePercent",
396
+			"template.strategy.rollingParams.maxUnavailable",
372 397
 		},
373
-		"invalid upper bound template.strategy.rollingParams.updatePercent": {
374
-			rollingConfigPct(1, 1, 1, 101),
398
+		"invalid percent template.strategy.rollingParams.maxSurge": {
399
+			rollingConfigMax(kutil.NewIntOrStringFromString("foo"), kutil.NewIntOrStringFromString("100%")),
375 400
 			fielderrors.ValidationErrorTypeInvalid,
376
-			"template.strategy.rollingParams.updatePercent",
377
-		},
378
-		"valid negative upper bound template.strategy.rollingParams.updatePercent": {
379
-			rollingConfigPct(1, 1, 1, -100),
380
-			"",
381
-			"",
382
-		},
383
-		"valid upper bound template.strategy.rollingParams.updatePercent": {
384
-			rollingConfigPct(1, 1, 1, 100),
385
-			"",
386
-			"",
387
-		},
388
-		"valid negative lower bound template.strategy.rollingParams.updatePercent": {
389
-			rollingConfigPct(1, 1, 1, -1),
390
-			"",
391
-			"",
392
-		},
393
-		"valid lower bound template.strategy.rollingParams.updatePercent": {
394
-			rollingConfigPct(1, 1, 1, 1),
395
-			"",
396
-			"",
401
+			"template.strategy.rollingParams.maxSurge",
397 402
 		},
398 403
 	}
399 404
 
... ...
@@ -9,3 +9,17 @@ type DeploymentStrategy interface {
9 9
 	// Deploy transitions an old deployment to a new one.
10 10
 	Deploy(from *kapi.ReplicationController, to *kapi.ReplicationController, desiredReplicas int) error
11 11
 }
12
+
13
+// UpdateAcceptor is given a chance to accept or reject the new controller
14
+// during a deployment each time the controller is scaled up.
15
+//
16
+// After the successful scale-up of the controller, the controller is given to
17
+// the UpdateAcceptor. If the UpdateAcceptor rejects the controller, the
18
+// deployment is stopped with an error.
19
+//
20
+// DEPRECATED: Acceptance checking has been incorporated into the rolling
21
+// strategy, but we still need this around to support Recreate.
22
+type UpdateAcceptor interface {
23
+	// Accept returns nil if the controller is okay, otherwise returns an error.
24
+	Accept(*kapi.ReplicationController) error
25
+}
... ...
@@ -13,6 +13,7 @@ import (
13 13
 	"k8s.io/kubernetes/pkg/util"
14 14
 
15 15
 	deployapi "github.com/openshift/origin/pkg/deploy/api"
16
+	strat "github.com/openshift/origin/pkg/deploy/strategy"
16 17
 	stratsupport "github.com/openshift/origin/pkg/deploy/strategy/support"
17 18
 	deployutil "github.com/openshift/origin/pkg/deploy/util"
18 19
 )
... ...
@@ -76,7 +77,7 @@ func (s *RecreateDeploymentStrategy) Deploy(from *kapi.ReplicationController, to
76 76
 //
77 77
 // This is currently only used in conjunction with the rolling update strategy
78 78
 // for initial deployments.
79
-func (s *RecreateDeploymentStrategy) DeployWithAcceptor(from *kapi.ReplicationController, to *kapi.ReplicationController, desiredReplicas int, updateAcceptor kubectl.UpdateAcceptor) error {
79
+func (s *RecreateDeploymentStrategy) DeployWithAcceptor(from *kapi.ReplicationController, to *kapi.ReplicationController, desiredReplicas int, updateAcceptor strat.UpdateAcceptor) error {
80 80
 	config, err := deployutil.DecodeDeploymentConfig(to, s.codec)
81 81
 	if err != nil {
82 82
 		return fmt.Errorf("couldn't decode config from deployment %s: %v", to.Name, err)
... ...
@@ -9,12 +9,11 @@ import (
9 9
 	kapi "k8s.io/kubernetes/pkg/api"
10 10
 	kclient "k8s.io/kubernetes/pkg/client"
11 11
 	"k8s.io/kubernetes/pkg/kubectl"
12
-	"k8s.io/kubernetes/pkg/labels"
13 12
 	"k8s.io/kubernetes/pkg/runtime"
14 13
 	"k8s.io/kubernetes/pkg/util"
15
-	"k8s.io/kubernetes/pkg/util/wait"
16 14
 
17 15
 	deployapi "github.com/openshift/origin/pkg/deploy/api"
16
+	strat "github.com/openshift/origin/pkg/deploy/strategy"
18 17
 	stratsupport "github.com/openshift/origin/pkg/deploy/strategy/support"
19 18
 	deployutil "github.com/openshift/origin/pkg/deploy/util"
20 19
 )
... ...
@@ -41,7 +40,7 @@ type RollingDeploymentStrategy struct {
41 41
 	// initialStrategy is used when there are no prior deployments.
42 42
 	initialStrategy acceptingDeploymentStrategy
43 43
 	// client is used to deal with ReplicationControllers.
44
-	client kubectl.RollingUpdaterClient
44
+	client kclient.Interface
45 45
 	// rollingUpdate knows how to perform a rolling update.
46 46
 	rollingUpdate func(config *kubectl.RollingUpdaterConfig) error
47 47
 	// codec is used to access the encoded config on a deployment.
... ...
@@ -50,7 +49,7 @@ type RollingDeploymentStrategy struct {
50 50
 	hookExecutor hookExecutor
51 51
 	// getUpdateAcceptor returns an UpdateAcceptor to verify the first replica
52 52
 	// of the deployment.
53
-	getUpdateAcceptor func(timeout time.Duration) kubectl.UpdateAcceptor
53
+	getUpdateAcceptor func(timeout time.Duration) strat.UpdateAcceptor
54 54
 }
55 55
 
56 56
 // acceptingDeploymentStrategy is a DeploymentStrategy which accepts an
... ...
@@ -59,7 +58,7 @@ type RollingDeploymentStrategy struct {
59 59
 // removed when https://github.com/kubernetes/kubernetes/pull/7183 is
60 60
 // fixed.
61 61
 type acceptingDeploymentStrategy interface {
62
-	DeployWithAcceptor(from *kapi.ReplicationController, to *kapi.ReplicationController, desiredReplicas int, updateAcceptor kubectl.UpdateAcceptor) error
62
+	DeployWithAcceptor(from *kapi.ReplicationController, to *kapi.ReplicationController, desiredReplicas int, updateAcceptor strat.UpdateAcceptor) error
63 63
 }
64 64
 
65 65
 // AcceptorInterval is how often the UpdateAcceptor should check for
... ...
@@ -68,36 +67,12 @@ const AcceptorInterval = 1 * time.Second
68 68
 
69 69
 // NewRollingDeploymentStrategy makes a new RollingDeploymentStrategy.
70 70
 func NewRollingDeploymentStrategy(namespace string, client kclient.Interface, codec runtime.Codec, initialStrategy acceptingDeploymentStrategy) *RollingDeploymentStrategy {
71
-	updaterClient := &rollingUpdaterClient{
72
-		ControllerHasDesiredReplicasFn: func(rc *kapi.ReplicationController) wait.ConditionFunc {
73
-			return kclient.ControllerHasDesiredReplicas(client, rc)
74
-		},
75
-		GetReplicationControllerFn: func(namespace, name string) (*kapi.ReplicationController, error) {
76
-			return client.ReplicationControllers(namespace).Get(name)
77
-		},
78
-		UpdateReplicationControllerFn: func(namespace string, rc *kapi.ReplicationController) (*kapi.ReplicationController, error) {
79
-			return client.ReplicationControllers(namespace).Update(rc)
80
-		},
81
-		// This guards against the RollingUpdater's built-in behavior to create
82
-		// RCs when the supplied old RC is nil. We won't pass nil, but it doesn't
83
-		// hurt to further guard against it since we would have no way to identify
84
-		// or clean up orphaned RCs RollingUpdater might inadvertently create.
85
-		CreateReplicationControllerFn: func(namespace string, rc *kapi.ReplicationController) (*kapi.ReplicationController, error) {
86
-			return nil, fmt.Errorf("unexpected attempt to create Deployment: %#v", rc)
87
-		},
88
-		// We give the RollingUpdater a policy which should prevent it from
89
-		// deleting the source deployment after the transition, but it doesn't
90
-		// hurt to guard by removing its ability to delete.
91
-		DeleteReplicationControllerFn: func(namespace, name string) error {
92
-			return fmt.Errorf("unexpected attempt to delete Deployment %s/%s", namespace, name)
93
-		},
94
-	}
95 71
 	return &RollingDeploymentStrategy{
96 72
 		codec:           codec,
97 73
 		initialStrategy: initialStrategy,
98
-		client:          updaterClient,
74
+		client:          client,
99 75
 		rollingUpdate: func(config *kubectl.RollingUpdaterConfig) error {
100
-			updater := kubectl.NewRollingUpdater(namespace, updaterClient)
76
+			updater := kubectl.NewRollingUpdater(namespace, client)
101 77
 			return updater.Update(config)
102 78
 		},
103 79
 		hookExecutor: &stratsupport.HookExecutor{
... ...
@@ -110,7 +85,7 @@ func NewRollingDeploymentStrategy(namespace string, client kclient.Interface, co
110 110
 				},
111 111
 			},
112 112
 		},
113
-		getUpdateAcceptor: func(timeout time.Duration) kubectl.UpdateAcceptor {
113
+		getUpdateAcceptor: func(timeout time.Duration) strat.UpdateAcceptor {
114 114
 			return stratsupport.NewAcceptNewlyObservedReadyPods(client, timeout, AcceptorInterval)
115 115
 		},
116 116
 	}
... ...
@@ -175,13 +150,13 @@ func (s *RollingDeploymentStrategy) Deploy(from *kapi.ReplicationController, to
175 175
 	//
176 176
 	// Related upstream issue:
177 177
 	// https://github.com/kubernetes/kubernetes/pull/7183
178
-	to, err = s.client.GetReplicationController(to.Namespace, to.Name)
178
+	to, err = s.client.ReplicationControllers(to.Namespace).Get(to.Name)
179 179
 	if err != nil {
180 180
 		return fmt.Errorf("couldn't look up deployment %s: %s", deployutil.LabelForDeployment(to), err)
181 181
 	}
182 182
 	if _, hasSourceId := to.Annotations[sourceIdAnnotation]; !hasSourceId {
183 183
 		to.Annotations[sourceIdAnnotation] = fmt.Sprintf("%s:%s", from.Name, from.ObjectMeta.UID)
184
-		if updated, err := s.client.UpdateReplicationController(to.Namespace, to); err != nil {
184
+		if updated, err := s.client.ReplicationControllers(to.Namespace).Update(to); err != nil {
185 185
 			return fmt.Errorf("couldn't assign source annotation to deployment %s: %v", deployutil.LabelForDeployment(to), err)
186 186
 		} else {
187 187
 			to = updated
... ...
@@ -205,24 +180,12 @@ func (s *RollingDeploymentStrategy) Deploy(from *kapi.ReplicationController, to
205 205
 		UpdatePeriod:   time.Duration(*params.UpdatePeriodSeconds) * time.Second,
206 206
 		Interval:       time.Duration(*params.IntervalSeconds) * time.Second,
207 207
 		Timeout:        time.Duration(*params.TimeoutSeconds) * time.Second,
208
-		UpdatePercent:  params.UpdatePercent,
209 208
 		CleanupPolicy:  kubectl.PreserveRollingUpdateCleanupPolicy,
210
-		UpdateAcceptor: updateAcceptor,
209
+		MaxSurge:       params.MaxSurge,
210
+		MaxUnavailable: params.MaxUnavailable,
211 211
 	}
212
-	pct := "<nil>"
213
-	if params.UpdatePercent != nil {
214
-		pct = fmt.Sprintf("%d", *params.UpdatePercent)
215
-	}
216
-	glog.Infof("Starting rolling update from %s to %s (desired replicas: %d, updatePeriodSeconds=%ds, intervalSeconds=%ds, timeoutSeconds=%ds, updatePercent=%s%%)",
217
-		deployutil.LabelForDeployment(from),
218
-		deployutil.LabelForDeployment(to),
219
-		desiredReplicas,
220
-		*params.UpdatePeriodSeconds,
221
-		*params.IntervalSeconds,
222
-		*params.TimeoutSeconds,
223
-		pct,
224
-	)
225
-	if err := s.rollingUpdate(rollingConfig); err != nil {
212
+	err = s.rollingUpdate(rollingConfig)
213
+	if err != nil {
226 214
 		return err
227 215
 	}
228 216
 
... ...
@@ -239,39 +202,6 @@ func (s *RollingDeploymentStrategy) Deploy(from *kapi.ReplicationController, to
239 239
 	return nil
240 240
 }
241 241
 
242
-type rollingUpdaterClient struct {
243
-	GetReplicationControllerFn     func(namespace, name string) (*kapi.ReplicationController, error)
244
-	UpdateReplicationControllerFn  func(namespace string, rc *kapi.ReplicationController) (*kapi.ReplicationController, error)
245
-	CreateReplicationControllerFn  func(namespace string, rc *kapi.ReplicationController) (*kapi.ReplicationController, error)
246
-	DeleteReplicationControllerFn  func(namespace, name string) error
247
-	ListReplicationControllersFn   func(namespace string, label labels.Selector) (*kapi.ReplicationControllerList, error)
248
-	ControllerHasDesiredReplicasFn func(rc *kapi.ReplicationController) wait.ConditionFunc
249
-}
250
-
251
-func (c *rollingUpdaterClient) GetReplicationController(namespace, name string) (*kapi.ReplicationController, error) {
252
-	return c.GetReplicationControllerFn(namespace, name)
253
-}
254
-
255
-func (c *rollingUpdaterClient) UpdateReplicationController(namespace string, rc *kapi.ReplicationController) (*kapi.ReplicationController, error) {
256
-	return c.UpdateReplicationControllerFn(namespace, rc)
257
-}
258
-
259
-func (c *rollingUpdaterClient) CreateReplicationController(namespace string, rc *kapi.ReplicationController) (*kapi.ReplicationController, error) {
260
-	return c.CreateReplicationControllerFn(namespace, rc)
261
-}
262
-
263
-func (c *rollingUpdaterClient) DeleteReplicationController(namespace, name string) error {
264
-	return c.DeleteReplicationControllerFn(namespace, name)
265
-}
266
-
267
-func (c *rollingUpdaterClient) ListReplicationControllers(namespace string, label labels.Selector) (*kapi.ReplicationControllerList, error) {
268
-	return c.ListReplicationControllersFn(namespace, label)
269
-}
270
-
271
-func (c *rollingUpdaterClient) ControllerHasDesiredReplicas(rc *kapi.ReplicationController) wait.ConditionFunc {
272
-	return c.ControllerHasDesiredReplicasFn(rc)
273
-}
274
-
275 242
 // rollingUpdaterWriter is an io.Writer that delegates to glog.
276 243
 type rollingUpdaterWriter struct{}
277 244
 
... ...
@@ -6,12 +6,14 @@ import (
6 6
 	"time"
7 7
 
8 8
 	kapi "k8s.io/kubernetes/pkg/api"
9
-	// kerrors "k8s.io/kubernetes/pkg/api/errors"
9
+	ktestclient "k8s.io/kubernetes/pkg/client/testclient"
10 10
 	"k8s.io/kubernetes/pkg/kubectl"
11
+	"k8s.io/kubernetes/pkg/runtime"
11 12
 
12 13
 	api "github.com/openshift/origin/pkg/api/latest"
13 14
 	deployapi "github.com/openshift/origin/pkg/deploy/api"
14 15
 	deploytest "github.com/openshift/origin/pkg/deploy/api/test"
16
+	strat "github.com/openshift/origin/pkg/deploy/strategy"
15 17
 	deployutil "github.com/openshift/origin/pkg/deploy/util"
16 18
 )
17 19
 
... ...
@@ -19,15 +21,10 @@ func TestRolling_deployInitial(t *testing.T) {
19 19
 	initialStrategyInvoked := false
20 20
 
21 21
 	strategy := &RollingDeploymentStrategy{
22
-		codec: api.Codec,
23
-		client: &rollingUpdaterClient{
24
-			GetReplicationControllerFn: func(namespace, name string) (*kapi.ReplicationController, error) {
25
-				t.Fatalf("unexpected call to GetReplicationController")
26
-				return nil, nil
27
-			},
28
-		},
22
+		codec:  api.Codec,
23
+		client: ktestclient.NewSimpleFake(),
29 24
 		initialStrategy: &testStrategy{
30
-			deployFn: func(from *kapi.ReplicationController, to *kapi.ReplicationController, desiredReplicas int, updateAcceptor kubectl.UpdateAcceptor) error {
25
+			deployFn: func(from *kapi.ReplicationController, to *kapi.ReplicationController, desiredReplicas int, updateAcceptor strat.UpdateAcceptor) error {
31 26
 				initialStrategyInvoked = true
32 27
 				return nil
33 28
 			},
... ...
@@ -63,25 +60,26 @@ func TestRolling_deployRolling(t *testing.T) {
63 63
 		latest.Name:     latest,
64 64
 		deployment.Name: deployment,
65 65
 	}
66
+	deploymentUpdated := false
67
+
68
+	fake := &ktestclient.Fake{}
69
+	fake.ReactFn = func(action ktestclient.Action) (runtime.Object, error) {
70
+		switch a := action.(type) {
71
+		case ktestclient.GetAction:
72
+			return deployments[a.GetName()], nil
73
+		case ktestclient.UpdateAction:
74
+			deploymentUpdated = true
75
+			return a.GetObject().(*kapi.ReplicationController), nil
76
+		}
77
+		return nil, nil
78
+	}
66 79
 
67 80
 	var rollingConfig *kubectl.RollingUpdaterConfig
68
-	deploymentUpdated := false
69 81
 	strategy := &RollingDeploymentStrategy{
70
-		codec: api.Codec,
71
-		client: &rollingUpdaterClient{
72
-			GetReplicationControllerFn: func(namespace, name string) (*kapi.ReplicationController, error) {
73
-				return deployments[name], nil
74
-			},
75
-			UpdateReplicationControllerFn: func(namespace string, rc *kapi.ReplicationController) (*kapi.ReplicationController, error) {
76
-				if rc.Name != deployment.Name {
77
-					t.Fatalf("unexpected call to UpdateReplicationController for %s", rc.Name)
78
-				}
79
-				deploymentUpdated = true
80
-				return rc, nil
81
-			},
82
-		},
82
+		codec:  api.Codec,
83
+		client: fake,
83 84
 		initialStrategy: &testStrategy{
84
-			deployFn: func(from *kapi.ReplicationController, to *kapi.ReplicationController, desiredReplicas int, updateAcceptor kubectl.UpdateAcceptor) error {
85
+			deployFn: func(from *kapi.ReplicationController, to *kapi.ReplicationController, desiredReplicas int, updateAcceptor strat.UpdateAcceptor) error {
85 86
 				t.Fatalf("unexpected call to initial strategy")
86 87
 				return nil
87 88
 			},
... ...
@@ -145,18 +143,22 @@ func TestRolling_deployRollingHooks(t *testing.T) {
145 145
 
146 146
 	deployments := map[string]*kapi.ReplicationController{latest.Name: latest}
147 147
 
148
+	fake := &ktestclient.Fake{}
149
+	fake.ReactFn = func(action ktestclient.Action) (runtime.Object, error) {
150
+		switch a := action.(type) {
151
+		case ktestclient.GetAction:
152
+			return deployments[a.GetName()], nil
153
+		case ktestclient.UpdateAction:
154
+			return a.GetObject().(*kapi.ReplicationController), nil
155
+		}
156
+		return nil, nil
157
+	}
158
+
148 159
 	strategy := &RollingDeploymentStrategy{
149
-		codec: api.Codec,
150
-		client: &rollingUpdaterClient{
151
-			GetReplicationControllerFn: func(namespace, name string) (*kapi.ReplicationController, error) {
152
-				return deployments[name], nil
153
-			},
154
-			UpdateReplicationControllerFn: func(namespace string, rc *kapi.ReplicationController) (*kapi.ReplicationController, error) {
155
-				return rc, nil
156
-			},
157
-		},
160
+		codec:  api.Codec,
161
+		client: fake,
158 162
 		initialStrategy: &testStrategy{
159
-			deployFn: func(from *kapi.ReplicationController, to *kapi.ReplicationController, desiredReplicas int, updateAcceptor kubectl.UpdateAcceptor) error {
163
+			deployFn: func(from *kapi.ReplicationController, to *kapi.ReplicationController, desiredReplicas int, updateAcceptor strat.UpdateAcceptor) error {
160 164
 				t.Fatalf("unexpected call to initial strategy")
161 165
 				return nil
162 166
 			},
... ...
@@ -211,18 +213,10 @@ func TestRolling_deployInitialHooks(t *testing.T) {
211 211
 	var hookError error
212 212
 
213 213
 	strategy := &RollingDeploymentStrategy{
214
-		codec: api.Codec,
215
-		client: &rollingUpdaterClient{
216
-			GetReplicationControllerFn: func(namespace, name string) (*kapi.ReplicationController, error) {
217
-				t.Fatalf("unexpected call to GetReplicationController")
218
-				return nil, nil
219
-			},
220
-			UpdateReplicationControllerFn: func(namespace string, rc *kapi.ReplicationController) (*kapi.ReplicationController, error) {
221
-				return rc, nil
222
-			},
223
-		},
214
+		codec:  api.Codec,
215
+		client: ktestclient.NewSimpleFake(),
224 216
 		initialStrategy: &testStrategy{
225
-			deployFn: func(from *kapi.ReplicationController, to *kapi.ReplicationController, desiredReplicas int, updateAcceptor kubectl.UpdateAcceptor) error {
217
+			deployFn: func(from *kapi.ReplicationController, to *kapi.ReplicationController, desiredReplicas int, updateAcceptor strat.UpdateAcceptor) error {
226 218
 				return nil
227 219
 			},
228 220
 		},
... ...
@@ -270,10 +264,10 @@ func TestRolling_deployInitialHooks(t *testing.T) {
270 270
 }
271 271
 
272 272
 type testStrategy struct {
273
-	deployFn func(from *kapi.ReplicationController, to *kapi.ReplicationController, desiredReplicas int, updateAcceptor kubectl.UpdateAcceptor) error
273
+	deployFn func(from *kapi.ReplicationController, to *kapi.ReplicationController, desiredReplicas int, updateAcceptor strat.UpdateAcceptor) error
274 274
 }
275 275
 
276
-func (s *testStrategy) DeployWithAcceptor(from *kapi.ReplicationController, to *kapi.ReplicationController, desiredReplicas int, updateAcceptor kubectl.UpdateAcceptor) error {
276
+func (s *testStrategy) DeployWithAcceptor(from *kapi.ReplicationController, to *kapi.ReplicationController, desiredReplicas int, updateAcceptor strat.UpdateAcceptor) error {
277 277
 	return s.deployFn(from, to, desiredReplicas, updateAcceptor)
278 278
 }
279 279
 
... ...
@@ -307,7 +301,7 @@ func rollingParams(preFailurePolicy, postFailurePolicy deployapi.LifecycleHookFa
307 307
 	}
308 308
 }
309 309
 
310
-func getUpdateAcceptor(timeout time.Duration) kubectl.UpdateAcceptor {
310
+func getUpdateAcceptor(timeout time.Duration) strat.UpdateAcceptor {
311 311
 	return &testAcceptor{
312 312
 		acceptFn: func(deployment *kapi.ReplicationController) error {
313 313
 			return nil