Browse code

Enable custom deployments to have rolling/recreate as well

Allow customParams to be specified when type is Recreate or Rolling.
Allow image to be empty, add validation for resources and environment.
Add a custom deployment extended test.

Clayton Coleman authored on 2016/05/08 12:55:12
Showing 9 changed files
... ...
@@ -29,13 +29,16 @@ type DeploymentStrategy struct {
29 29
 	// Type is the name of a deployment strategy.
30 30
 	Type DeploymentStrategyType
31 31
 
32
-	// CustomParams are the input to the Custom deployment strategy.
33
-	CustomParams *CustomDeploymentStrategyParams
34 32
 	// RecreateParams are the input to the Recreate deployment strategy.
35 33
 	RecreateParams *RecreateDeploymentStrategyParams
36 34
 	// RollingParams are the input to the Rolling deployment strategy.
37 35
 	RollingParams *RollingDeploymentStrategyParams
38 36
 
37
+	// CustomParams are the input to the Custom deployment strategy, and may also
38
+	// be specified for the Recreate and Rolling strategies to customize the execution
39
+	// process that runs the deployment.
40
+	CustomParams *CustomDeploymentStrategyParams
41
+
39 42
 	// Resources contains resource requirements to execute the deployment
40 43
 	Resources kapi.ResourceRequirements
41 44
 	// Labels is a set of key, value pairs added to custom deployer and lifecycle pre/post hook pods.
... ...
@@ -50,7 +53,7 @@ type DeploymentStrategyType string
50 50
 const (
51 51
 	// DeploymentStrategyTypeRecreate is a simple strategy suitable as a default.
52 52
 	DeploymentStrategyTypeRecreate DeploymentStrategyType = "Recreate"
53
-	// DeploymentStrategyTypeCustom is a user defined strategy.
53
+	// DeploymentStrategyTypeCustom is a user defined strategy. It is optional to set.
54 54
 	DeploymentStrategyTypeCustom DeploymentStrategyType = "Custom"
55 55
 	// DeploymentStrategyTypeRolling uses the Kubernetes RollingUpdater.
56 56
 	DeploymentStrategyTypeRolling DeploymentStrategyType = "Rolling"
... ...
@@ -110,6 +110,10 @@ func validateDeploymentStrategy(strategy *deployapi.DeploymentStrategy, pod *kap
110 110
 		errs = append(errs, field.Required(fldPath.Child("type"), ""))
111 111
 	}
112 112
 
113
+	if strategy.CustomParams != nil {
114
+		errs = append(errs, validateCustomParams(strategy.CustomParams, fldPath.Child("customParams"))...)
115
+	}
116
+
113 117
 	switch strategy.Type {
114 118
 	case deployapi.DeploymentStrategyTypeRecreate:
115 119
 		if strategy.RecreateParams != nil {
... ...
@@ -124,8 +128,12 @@ func validateDeploymentStrategy(strategy *deployapi.DeploymentStrategy, pod *kap
124 124
 	case deployapi.DeploymentStrategyTypeCustom:
125 125
 		if strategy.CustomParams == nil {
126 126
 			errs = append(errs, field.Required(fldPath.Child("customParams"), ""))
127
-		} else {
128
-			errs = append(errs, validateCustomParams(strategy.CustomParams, fldPath.Child("customParams"))...)
127
+		}
128
+		if strategy.RollingParams != nil {
129
+			errs = append(errs, validateRollingParams(strategy.RollingParams, pod, fldPath.Child("rollingParams"))...)
130
+		}
131
+		if strategy.RecreateParams != nil {
132
+			errs = append(errs, validateRecreateParams(strategy.RecreateParams, pod, fldPath.Child("recreateParams"))...)
129 133
 		}
130 134
 	case "":
131 135
 		errs = append(errs, field.Required(fldPath.Child("type"), "strategy type is required"))
... ...
@@ -140,7 +148,7 @@ func validateDeploymentStrategy(strategy *deployapi.DeploymentStrategy, pod *kap
140 140
 		errs = append(errs, validation.ValidateAnnotations(strategy.Annotations, fldPath.Child("annotations"))...)
141 141
 	}
142 142
 
143
-	// TODO: validate resource requirements (prereq: https://github.com/kubernetes/kubernetes/pull/7059)
143
+	errs = append(errs, validation.ValidateResourceRequirements(&strategy.Resources, fldPath.Child("resources"))...)
144 144
 
145 145
 	return errs
146 146
 }
... ...
@@ -148,9 +156,7 @@ func validateDeploymentStrategy(strategy *deployapi.DeploymentStrategy, pod *kap
148 148
 func validateCustomParams(params *deployapi.CustomDeploymentStrategyParams, fldPath *field.Path) field.ErrorList {
149 149
 	errs := field.ErrorList{}
150 150
 
151
-	if len(params.Image) == 0 {
152
-		errs = append(errs, field.Required(fldPath.Child("image"), ""))
153
-	}
151
+	errs = append(errs, validateEnv(params.Environment, fldPath.Child("environment"))...)
154 152
 
155 153
 	return errs
156 154
 }
... ...
@@ -235,7 +241,7 @@ func validateEnv(vars []kapi.EnvVar, fldPath *field.Path) field.ErrorList {
235 235
 
236 236
 	for i, ev := range vars {
237 237
 		vErrs := field.ErrorList{}
238
-		idxPath := fldPath.Child("name").Index(i)
238
+		idxPath := fldPath.Index(i).Child("name")
239 239
 		if len(ev.Name) == 0 {
240 240
 			vErrs = append(vErrs, field.Required(idxPath, ""))
241 241
 		}
... ...
@@ -240,7 +240,7 @@ func TestValidateDeploymentConfigMissingFields(t *testing.T) {
240 240
 			field.ErrorTypeRequired,
241 241
 			"spec.strategy.customParams",
242 242
 		},
243
-		"missing spec.strategy.customParams.image": {
243
+		"invalid spec.strategy.customParams.environment": {
244 244
 			api.DeploymentConfig{
245 245
 				ObjectMeta: kapi.ObjectMeta{Name: "foo", Namespace: "bar"},
246 246
 				Spec: api.DeploymentConfigSpec{
... ...
@@ -248,14 +248,18 @@ func TestValidateDeploymentConfigMissingFields(t *testing.T) {
248 248
 					Triggers: manualTrigger(),
249 249
 					Selector: test.OkSelector(),
250 250
 					Strategy: api.DeploymentStrategy{
251
-						Type:         api.DeploymentStrategyTypeCustom,
252
-						CustomParams: &api.CustomDeploymentStrategyParams{},
251
+						Type: api.DeploymentStrategyTypeCustom,
252
+						CustomParams: &api.CustomDeploymentStrategyParams{
253
+							Environment: []kapi.EnvVar{
254
+								{Name: "A=B"},
255
+							},
256
+						},
253 257
 					},
254 258
 					Template: test.OkPodTemplate(),
255 259
 				},
256 260
 			},
257
-			field.ErrorTypeRequired,
258
-			"spec.strategy.customParams.image",
261
+			field.ErrorTypeInvalid,
262
+			"spec.strategy.customParams.environment[0].name",
259 263
 		},
260 264
 		"missing spec.strategy.recreateParams.pre.failurePolicy": {
261 265
 			api.DeploymentConfig{
... ...
@@ -245,6 +245,8 @@ func (c *DeploymentController) makeDeployerPod(deployment *kapi.ReplicationContr
245 245
 				},
246 246
 			},
247 247
 			ActiveDeadlineSeconds: &maxDeploymentDurationSeconds,
248
+			DNSPolicy:             deployment.Spec.Template.Spec.DNSPolicy,
249
+			ImagePullSecrets:      deployment.Spec.Template.Spec.ImagePullSecrets,
248 250
 			// Setting the node selector on the deployer pod so that it is created
249 251
 			// on the same set of nodes as the pods.
250 252
 			NodeSelector:       deployment.Spec.Template.Spec.NodeSelector,
... ...
@@ -12,6 +12,7 @@ import (
12 12
 	"k8s.io/kubernetes/pkg/runtime"
13 13
 	"k8s.io/kubernetes/pkg/util/flowcontrol"
14 14
 	utilruntime "k8s.io/kubernetes/pkg/util/runtime"
15
+	"k8s.io/kubernetes/pkg/util/sets"
15 16
 	"k8s.io/kubernetes/pkg/watch"
16 17
 
17 18
 	controller "github.com/openshift/origin/pkg/controller"
... ...
@@ -135,32 +136,42 @@ func (factory *DeploymentControllerFactory) Create() controller.RunnableControll
135 135
 //   1. For the Recreate and Rolling strategies, strategy, use the factory's
136 136
 //      DeployerImage as the container image, and the factory's Environment
137 137
 //      as the container environment.
138
-//   2. For all Custom strategy, use the strategy's image for the container
139
-//      image, and use the combination of the factory's Environment and the
140
-//      strategy's environment as the container environment.
138
+//   2. For all Custom strategies, or if the CustomParams field is set, use
139
+//      the strategy's image for the container image, and use the combination
140
+//      of the factory's Environment and the strategy's environment as the
141
+//      container environment.
142
+//
141 143
 func (factory *DeploymentControllerFactory) makeContainer(strategy *deployapi.DeploymentStrategy) *kapi.Container {
142
-	// Set default environment values
143
-	environment := []kapi.EnvVar{}
144
-	for _, env := range factory.Environment {
145
-		environment = append(environment, env)
146
-	}
144
+	image := factory.DeployerImage
145
+	var environment []kapi.EnvVar
146
+	var command []string
147 147
 
148
-	// Every strategy type should be handled here.
149
-	switch strategy.Type {
150
-	case deployapi.DeploymentStrategyTypeRecreate, deployapi.DeploymentStrategyTypeRolling:
151
-		// Use the factory-configured image.
152
-	case deployapi.DeploymentStrategyTypeCustom:
153
-		// Use user-defined values from the strategy input.
148
+	set := sets.NewString()
149
+	// Use user-defined values from the strategy input.
150
+	if p := strategy.CustomParams; p != nil {
151
+		if len(p.Image) > 0 {
152
+			image = p.Image
153
+		}
154
+		if len(p.Command) > 0 {
155
+			command = p.Command
156
+		}
154 157
 		for _, env := range strategy.CustomParams.Environment {
158
+			set.Insert(env.Name)
155 159
 			environment = append(environment, env)
156 160
 		}
157
-		return &kapi.Container{
158
-			Image: strategy.CustomParams.Image,
159
-			Env:   environment,
161
+	}
162
+
163
+	// Set default environment values
164
+	for _, env := range factory.Environment {
165
+		if set.Has(env.Name) {
166
+			continue
160 167
 		}
168
+		environment = append(environment, env)
161 169
 	}
170
+
162 171
 	return &kapi.Container{
163
-		Image: factory.DeployerImage,
164
-		Env:   environment,
172
+		Image:   image,
173
+		Command: command,
174
+		Env:     environment,
165 175
 	}
166 176
 }
... ...
@@ -160,7 +160,7 @@ func (e *HookExecutor) executeExecNewPod(hook *deployapi.LifecycleHook, deployme
160 160
 
161 161
 	// Track whether the pod has already run to completion and avoid showing logs
162 162
 	// or the Success message twice.
163
-	completed := false
163
+	completed, created := false, false
164 164
 
165 165
 	// Try to create the pod.
166 166
 	pod, err := e.podClient.CreatePod(deployment.Namespace, podSpec)
... ...
@@ -172,6 +172,7 @@ func (e *HookExecutor) executeExecNewPod(hook *deployapi.LifecycleHook, deployme
172 172
 		pod = podSpec
173 173
 		pod.Namespace = deployment.Namespace
174 174
 	} else {
175
+		created = true
175 176
 		fmt.Fprintf(e.out, "--> %s: Running hook pod ...\n", label)
176 177
 	}
177 178
 
... ...
@@ -200,7 +201,9 @@ waitLoop:
200 200
 				wg.Done()
201 201
 				break waitLoop
202 202
 			}
203
-			fmt.Fprintf(e.out, "--> %s: Hook pod is already running ...\n", label)
203
+			if !created {
204
+				fmt.Fprintf(e.out, "--> %s: Hook pod is already running ...\n", label)
205
+			}
204 206
 			go once.Do(func() { e.readPodLogs(pod, wg) })
205 207
 			break waitLoop
206 208
 		default:
... ...
@@ -27,6 +27,7 @@ var _ = g.Describe("deploymentconfigs", func() {
27 27
 	var (
28 28
 		deploymentFixture       = exutil.FixturePath("..", "extended", "fixtures", "test-deployment-test.yaml")
29 29
 		simpleDeploymentFixture = exutil.FixturePath("..", "extended", "fixtures", "deployment-simple.yaml")
30
+		customDeploymentFixture = exutil.FixturePath("..", "extended", "fixtures", "custom-deployment.yaml")
30 31
 		oc                      = exutil.NewCLI("cli-deployment", exutil.KubeConfigPath())
31 32
 	)
32 33
 
... ...
@@ -186,6 +187,29 @@ var _ = g.Describe("deploymentconfigs", func() {
186 186
 			}
187 187
 		})
188 188
 	})
189
+
190
+	g.Describe("with custom deployments", func() {
191
+		g.It("should run the custom deployment steps [Conformance]", func() {
192
+			out, err := oc.Run("create").Args("-f", customDeploymentFixture).Output()
193
+			o.Expect(err).NotTo(o.HaveOccurred())
194
+
195
+			o.Expect(waitForLatestCondition(oc, "custom-deployment", deploymentRunTimeout, deploymentRunning)).NotTo(o.HaveOccurred())
196
+
197
+			out, err = oc.Run("logs").Args("-f", "dc/custom-deployment").Output()
198
+			o.Expect(err).NotTo(o.HaveOccurred())
199
+			g.By(fmt.Sprintf("checking the logs for substrings\n%s", out))
200
+			o.Expect(out).To(o.ContainSubstring("--> pre: Running hook pod ..."))
201
+			o.Expect(out).To(o.ContainSubstring("test pre hook executed"))
202
+			o.Expect(out).To(o.ContainSubstring("--> Scaling custom-deployment-1 to 2"))
203
+			o.Expect(out).To(o.ContainSubstring("--> Reached 50%"))
204
+			o.Expect(out).To(o.ContainSubstring("Halfway"))
205
+			o.Expect(out).To(o.ContainSubstring("Finished"))
206
+			o.Expect(out).To(o.ContainSubstring("--> Success"))
207
+
208
+			g.By("verifying the deployment is marked complete")
209
+			o.Expect(waitForLatestCondition(oc, "custom-deployment", deploymentRunTimeout, deploymentReachedCompletion)).NotTo(o.HaveOccurred())
210
+		})
211
+	})
189 212
 })
190 213
 
191 214
 func deploymentStatuses(rcs []kapi.ReplicationController) []string {
192 215
new file mode 100644
... ...
@@ -0,0 +1,43 @@
0
+apiVersion: v1
1
+kind: DeploymentConfig
2
+metadata:
3
+  name: custom-deployment
4
+spec:
5
+  replicas: 2
6
+  selector:
7
+    name: custom-deployment
8
+  strategy:
9
+    type: Rolling
10
+    rollingParams:
11
+      pre:
12
+        failurePolicy: Abort
13
+        execNewPod:
14
+          containerName: myapp
15
+          command:
16
+          - /bin/echo
17
+          - test pre hook executed
18
+    customParams:
19
+      command:
20
+      - /bin/sh
21
+      - -c
22
+      - |
23
+        set -e
24
+        openshift-deploy --until=50%
25
+        echo Halfway
26
+        openshift-deploy
27
+        echo Finished
28
+  template:
29
+    metadata:
30
+      labels:
31
+        name: custom-deployment
32
+    spec:
33
+      terminationGracePeriodSeconds: 0
34
+      containers:
35
+      - image: "docker.io/centos:centos7"
36
+        imagePullPolicy: IfNotPresent
37
+        name: myapp
38
+        command:
39
+        - /bin/sleep
40
+        - "100"
41
+  triggers:
42
+  - type: ConfigChange
... ...
@@ -21,6 +21,7 @@ spec:
21 21
       labels:
22 22
         name: deployment-test
23 23
     spec:
24
+      terminationGracePeriodSeconds: 0
24 25
       containers:
25 26
       - image: "docker.io/centos:centos7"
26 27
         imagePullPolicy: IfNotPresent