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.
| ... | ... |
@@ -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 |