Skip to content

Commit f0913b2

Browse files
committed
Never deploy unspecified images in DC
1 parent d9dcac5 commit f0913b2

File tree

3 files changed

+118
-46
lines changed

3 files changed

+118
-46
lines changed

pkg/apps/controller/deploymentconfig/deploymentconfig_controller.go

+71-42
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ func (c *DeploymentConfigController) Handle(config *appsapi.DeploymentConfig) er
8787
glog.V(5).Infof("Reconciling %s/%s", config.Namespace, config.Name)
8888
// There's nothing to reconcile until the version is nonzero.
8989
if appsutil.IsInitialDeployment(config) && !appsutil.HasTrigger(config) {
90-
return c.updateStatus(config, []*v1.ReplicationController{})
90+
return c.updateStatus(config, []*v1.ReplicationController{}, true)
9191
}
9292

9393
// List all ReplicationControllers to find also those we own but that no longer match our selector.
@@ -118,7 +118,18 @@ func (c *DeploymentConfigController) Handle(config *appsapi.DeploymentConfig) er
118118
// the latest available information. Some deletions make take some time to complete so there
119119
// is value in doing this.
120120
if config.DeletionTimestamp != nil {
121-
return c.updateStatus(config, existingDeployments)
121+
return c.updateStatus(config, existingDeployments, true)
122+
}
123+
124+
// If the config is paused we shouldn't create new deployments for it.
125+
if config.Spec.Paused {
126+
// in order for revision history limit cleanup to work for paused
127+
// deployments, we need to trigger it here
128+
if err := c.cleanupOldDeployments(existingDeployments, config); err != nil {
129+
c.recorder.Eventf(config, v1.EventTypeWarning, "DeploymentCleanupFailed", "Couldn't clean up deployments: %v", err)
130+
}
131+
132+
return c.updateStatus(config, existingDeployments, true)
122133
}
123134

124135
latestExists, latestDeployment := appsutil.LatestDeploymentInfo(config, existingDeployments)
@@ -128,38 +139,52 @@ func (c *DeploymentConfigController) Handle(config *appsapi.DeploymentConfig) er
128139
return err
129140
}
130141
}
142+
143+
// Never deploy with invalid or unresolved images
144+
for i, container := range config.Spec.Template.Spec.Containers {
145+
if len(strings.TrimSpace(container.Image)) == 0 {
146+
glog.V(4).Infof("Postponing rollout #%d for DeploymentConfig %s/%s because of invalid or unresolved image for container #%d (name=%s)", config.Status.LatestVersion, config.Namespace, config.Name, i, container.Name)
147+
return c.updateStatus(config, existingDeployments, true)
148+
}
149+
}
150+
131151
configCopy := config.DeepCopy()
132152
// Process triggers and start an initial rollouts
133-
shouldTrigger, shouldSkip := triggerActivated(configCopy, latestExists, latestDeployment, c.codec)
134-
if !shouldSkip && shouldTrigger {
135-
configCopy.Status.LatestVersion++
136-
return c.updateStatus(configCopy, existingDeployments)
153+
shouldTrigger, shouldSkip, err := triggerActivated(configCopy, latestExists, latestDeployment, c.codec)
154+
if err != nil {
155+
return fmt.Errorf("triggerActivated failed: %v", err)
137156
}
138-
// Have to wait for the image trigger to get the image before proceeding.
139-
if shouldSkip && appsutil.IsInitialDeployment(config) {
140-
return c.updateStatus(configCopy, existingDeployments)
157+
158+
if shouldSkip {
159+
return c.updateStatus(configCopy, existingDeployments, true)
141160
}
161+
162+
if shouldTrigger {
163+
configCopy.Status.LatestVersion++
164+
_, err := c.dn.DeploymentConfigs(configCopy.Namespace).UpdateStatus(configCopy)
165+
return err
166+
}
167+
142168
// If the latest deployment already exists, reconcile existing deployments
143169
// and return early.
144170
if latestExists {
145171
// If the latest deployment is still running, try again later. We don't
146172
// want to compete with the deployer.
147173
if !appsutil.IsTerminatedDeployment(latestDeployment) {
148-
return c.updateStatus(config, existingDeployments)
174+
return c.updateStatus(config, existingDeployments, false)
149175
}
150176

151177
return c.reconcileDeployments(existingDeployments, config, cm)
152178
}
153-
// If the config is paused we shouldn't create new deployments for it.
154-
if config.Spec.Paused {
155-
// in order for revision history limit cleanup to work for paused
156-
// deployments, we need to trigger it here
157-
if err := c.cleanupOldDeployments(existingDeployments, config); err != nil {
158-
c.recorder.Eventf(config, v1.EventTypeWarning, "DeploymentCleanupFailed", "Couldn't clean up deployments: %v", err)
159-
}
160179

161-
return c.updateStatus(config, existingDeployments)
180+
// Never deploy with invalid or unresolved images
181+
for i, container := range config.Spec.Template.Spec.Containers {
182+
if len(strings.TrimSpace(container.Image)) == 0 {
183+
glog.V(4).Infof("Postponing rollout #%d for DeploymentConfig %s/%s because of invalid or unresolved image for container #%d (name=%s)", config.Status.LatestVersion, config.Namespace, config.Name, i, container.Name)
184+
return c.updateStatus(config, existingDeployments, true)
185+
}
162186
}
187+
163188
// No deployments are running and the latest deployment doesn't exist, so
164189
// create the new deployment.
165190
deployment, err := appsutil.MakeDeploymentV1(config, c.codec)
@@ -182,17 +207,17 @@ func (c *DeploymentConfigController) Handle(config *appsapi.DeploymentConfig) er
182207
if isOurs {
183208
// If the deployment was already created, just move on. The cache could be
184209
// stale, or another process could have already handled this update.
185-
return c.updateStatus(config, existingDeployments)
210+
return c.updateStatus(config, existingDeployments, true)
186211
} else {
187-
err = fmt.Errorf("replication controller %s already exists and deployment config is not allowed to claim it.", deployment.Name)
212+
err = fmt.Errorf("replication controller %s already exists and deployment config is not allowed to claim it", deployment.Name)
188213
c.recorder.Eventf(config, v1.EventTypeWarning, "DeploymentCreationFailed", "Couldn't deploy version %d: %v", config.Status.LatestVersion, err)
189-
return c.updateStatus(config, existingDeployments)
214+
return c.updateStatus(config, existingDeployments, true)
190215
}
191216
}
192217
c.recorder.Eventf(config, v1.EventTypeWarning, "DeploymentCreationFailed", "Couldn't deploy version %d: %s", config.Status.LatestVersion, err)
193218
// We don't care about this error since we need to report the create failure.
194219
cond := appsutil.NewDeploymentCondition(appsapi.DeploymentProgressing, kapi.ConditionFalse, appsapi.FailedRcCreateReason, err.Error())
195-
_ = c.updateStatus(config, existingDeployments, *cond)
220+
_ = c.updateStatus(config, existingDeployments, true, *cond)
196221
return fmt.Errorf("couldn't create deployment for deployment config %s: %v", appsutil.LabelForDeploymentConfig(config), err)
197222
}
198223
msg := fmt.Sprintf("Created new replication controller %q for version %d", created.Name, config.Status.LatestVersion)
@@ -206,7 +231,7 @@ func (c *DeploymentConfigController) Handle(config *appsapi.DeploymentConfig) er
206231
}
207232

208233
cond := appsutil.NewDeploymentCondition(appsapi.DeploymentProgressing, kapi.ConditionTrue, appsapi.NewReplicationControllerReason, msg)
209-
return c.updateStatus(config, existingDeployments, *cond)
234+
return c.updateStatus(config, existingDeployments, true, *cond)
210235
}
211236

212237
// reconcileDeployments reconciles existing deployment replica counts which
@@ -285,13 +310,13 @@ func (c *DeploymentConfigController) reconcileDeployments(existingDeployments []
285310
c.recorder.Eventf(config, v1.EventTypeWarning, "ReplicationControllerCleanupFailed", "Couldn't clean up replication controllers: %v", err)
286311
}
287312

288-
return c.updateStatus(config, updatedDeployments)
313+
return c.updateStatus(config, updatedDeployments, true)
289314
}
290315

291316
// Update the status of the provided deployment config. Additional conditions will override any other condition in the
292317
// deployment config status.
293-
func (c *DeploymentConfigController) updateStatus(config *appsapi.DeploymentConfig, deployments []*v1.ReplicationController, additional ...appsapi.DeploymentCondition) error {
294-
newStatus := calculateStatus(config, deployments, additional...)
318+
func (c *DeploymentConfigController) updateStatus(config *appsapi.DeploymentConfig, deployments []*v1.ReplicationController, updateObservedGeneration bool, additional ...appsapi.DeploymentCondition) error {
319+
newStatus := calculateStatus(config, deployments, updateObservedGeneration, additional...)
295320

296321
// NOTE: We should update the status of the deployment config only if we need to, otherwise
297322
// we hotloop between updates.
@@ -376,7 +401,7 @@ func (c *DeploymentConfigController) cancelRunningRollouts(config *appsapi.Deplo
376401
return nil
377402
}
378403

379-
func calculateStatus(config *appsapi.DeploymentConfig, rcs []*v1.ReplicationController, additional ...appsapi.DeploymentCondition) appsapi.DeploymentConfigStatus {
404+
func calculateStatus(config *appsapi.DeploymentConfig, rcs []*v1.ReplicationController, updateObservedGeneration bool, additional ...appsapi.DeploymentCondition) appsapi.DeploymentConfigStatus {
380405
// UpdatedReplicas represents the replicas that use the current deployment config template which means
381406
// we should inform about the replicas of the latest deployment and not the active.
382407
latestReplicas := int32(0)
@@ -394,10 +419,15 @@ func calculateStatus(config *appsapi.DeploymentConfig, rcs []*v1.ReplicationCont
394419
unavailableReplicas = 0
395420
}
396421

422+
generation := config.Status.ObservedGeneration
423+
if updateObservedGeneration {
424+
generation = config.Generation
425+
}
426+
397427
status := appsapi.DeploymentConfigStatus{
398428
LatestVersion: config.Status.LatestVersion,
399429
Details: config.Status.Details,
400-
ObservedGeneration: config.Generation,
430+
ObservedGeneration: generation,
401431
Replicas: appsutil.GetStatusReplicaCountForDeployments(rcs),
402432
UpdatedReplicas: latestReplicas,
403433
AvailableReplicas: available,
@@ -519,17 +549,17 @@ func (c *DeploymentConfigController) cleanupOldDeployments(existingDeployments [
519549
// triggers were activated (config change or image change). The first bool indicates that
520550
// the triggers are active and second indicates if we should skip the rollout because we
521551
// are waiting for the trigger to complete update (waiting for image for example).
522-
func triggerActivated(config *appsapi.DeploymentConfig, latestExists bool, latestDeployment *v1.ReplicationController, codec runtime.Codec) (bool, bool) {
552+
func triggerActivated(config *appsapi.DeploymentConfig, latestExists bool, latestDeployment *v1.ReplicationController, codec runtime.Codec) (bool, bool, error) {
523553
if config.Spec.Paused {
524-
return false, false
554+
return false, false, nil
525555
}
526556
imageTrigger := appsutil.HasImageChangeTrigger(config)
527557
configTrigger := appsutil.HasChangeTrigger(config)
528558
hasTrigger := imageTrigger || configTrigger
529559

530560
// no-op when no triggers are defined.
531561
if !hasTrigger {
532-
return false, false
562+
return false, false, nil
533563
}
534564

535565
// Handle initial rollouts
@@ -542,50 +572,49 @@ func triggerActivated(config *appsapi.DeploymentConfig, latestExists bool, lates
542572
// TODO: Technically this is not a config change cause, but we will have to report the image that caused the trigger.
543573
// In some cases it might be difficult because config can have multiple ICT.
544574
appsutil.RecordConfigChangeCause(config)
545-
return true, false
575+
return true, false, nil
546576
}
547577
glog.V(4).Infof("Rolling out initial deployment for %s/%s deferred until its images are ready", config.Namespace, config.Name)
548-
return false, true
578+
return false, true, nil
549579
}
550580
// Rollout if we only have config change trigger.
551581
if configTrigger {
552582
glog.V(4).Infof("Rolling out initial deployment for %s/%s", config.Namespace, config.Name)
553583
appsutil.RecordConfigChangeCause(config)
554-
return true, false
584+
return true, false, nil
555585
}
556586
// We are waiting for the initial RC to be created.
557-
return false, false
587+
return false, false, nil
558588
}
559589

560590
// Wait for the RC to be created
561591
if !latestExists {
562-
return false, true
592+
return false, false, nil
563593
}
564594

565595
// We need existing deployment at this point to compare its template with current config template.
566596
if latestDeployment == nil {
567-
return false, false
597+
return false, false, nil
568598
}
569599

570600
if imageTrigger {
571601
if ok, imageNames := appsutil.HasUpdatedImages(config, latestDeployment); ok {
572602
glog.V(4).Infof("Rolling out #%d deployment for %s/%s caused by image changes (%s)", config.Status.LatestVersion+1, config.Namespace, config.Name, strings.Join(imageNames, ","))
573603
appsutil.RecordImageChangeCauses(config, imageNames)
574-
return true, false
604+
return true, false, nil
575605
}
576606
}
577607

578608
if configTrigger {
579609
isLatest, changes, err := appsutil.HasLatestPodTemplate(config, latestDeployment, codec)
580610
if err != nil {
581-
glog.Errorf("Error while checking for latest pod template in replication controller: %v", err)
582-
return false, true
611+
return false, false, fmt.Errorf("error while checking for latest pod template in replication controller: %v", err)
583612
}
584613
if !isLatest {
585614
glog.V(4).Infof("Rolling out #%d deployment for %s/%s caused by config change, diff: %s", config.Status.LatestVersion+1, config.Namespace, config.Name, changes)
586615
appsutil.RecordConfigChangeCause(config)
587-
return true, false
616+
return true, false, nil
588617
}
589618
}
590-
return false, false
619+
return false, false, nil
591620
}

pkg/apps/controller/deploymentconfig/deploymentconfig_controller_test.go

+30-3
Original file line numberDiff line numberDiff line change
@@ -452,6 +452,9 @@ func newInt32(i int32) *int32 {
452452

453453
func newDC(version, replicas, maxUnavailable int, cond appsapi.DeploymentCondition) *appsapi.DeploymentConfig {
454454
return &appsapi.DeploymentConfig{
455+
ObjectMeta: metav1.ObjectMeta{
456+
Generation: 1,
457+
},
455458
Spec: appsapi.DeploymentConfigSpec{
456459
Replicas: int32(replicas),
457460
Strategy: appsapi.DeploymentStrategy{
@@ -502,8 +505,9 @@ func TestCalculateStatus(t *testing.T) {
502505
tests := []struct {
503506
name string
504507

505-
dc *appsapi.DeploymentConfig
506-
rcs []*v1.ReplicationController
508+
dc *appsapi.DeploymentConfig
509+
rcs []*v1.ReplicationController
510+
updateObservedGeneration bool
507511

508512
expected appsapi.DeploymentConfigStatus
509513
}{
@@ -528,6 +532,29 @@ func TestCalculateStatus(t *testing.T) {
528532
},
529533
},
530534
},
535+
{
536+
name: "available deployment with updating observedGeneration",
537+
538+
dc: newDC(3, 3, 1, availableCond),
539+
rcs: []*v1.ReplicationController{
540+
newRC(3, 2, 2, 1, 1),
541+
newRC(2, 0, 0, 0, 0),
542+
newRC(1, 0, 1, 1, 1),
543+
},
544+
updateObservedGeneration: true,
545+
546+
expected: appsapi.DeploymentConfigStatus{
547+
LatestVersion: int64(3),
548+
ObservedGeneration: int64(1),
549+
Replicas: int32(3),
550+
ReadyReplicas: int32(2),
551+
AvailableReplicas: int32(2),
552+
UpdatedReplicas: int32(2),
553+
Conditions: []appsapi.DeploymentCondition{
554+
availableCond,
555+
},
556+
},
557+
},
531558
{
532559
name: "unavailable deployment",
533560

@@ -552,7 +579,7 @@ func TestCalculateStatus(t *testing.T) {
552579
}
553580

554581
for _, test := range tests {
555-
status := calculateStatus(test.dc, test.rcs)
582+
status := calculateStatus(test.dc, test.rcs, test.updateObservedGeneration)
556583
if !reflect.DeepEqual(status, test.expected) {
557584
t.Errorf("%s: expected status:\n%+v\ngot status:\n%+v", test.name, test.expected, status)
558585
}

test/extended/deployments/deployments.go

+17-1
Original file line numberDiff line numberDiff line change
@@ -700,13 +700,15 @@ var _ = g.Describe("[Feature:DeploymentConfig] deploymentconfigs", func() {
700700
})
701701

702702
g.Describe("paused [Conformance]", func() {
703+
dcName := "paused"
703704
g.AfterEach(func() {
704-
failureTrap(oc, "paused", g.CurrentGinkgoTestDescription().Failed)
705+
failureTrap(oc, dcName, g.CurrentGinkgoTestDescription().Failed)
705706
})
706707

707708
g.It("should disable actions on deployments", func() {
708709
resource, name, err := createFixture(oc, pausedDeploymentFixture)
709710
o.Expect(err).NotTo(o.HaveOccurred())
711+
o.Expect(name).To(o.Equal(dcName))
710712

711713
_, rcs, _, err := deploymentInfo(oc, name)
712714
o.Expect(err).NotTo(o.HaveOccurred())
@@ -756,6 +758,20 @@ var _ = g.Describe("[Feature:DeploymentConfig] deploymentconfigs", func() {
756758
})
757759
o.Expect(err).NotTo(o.HaveOccurred())
758760
o.Expect(waitForLatestCondition(oc, name, deploymentRunTimeout, deploymentReachedCompletion)).NotTo(o.HaveOccurred())
761+
762+
g.By("making sure it updates observedGeneration after being paused")
763+
dc, err := oc.AppsClient().Apps().DeploymentConfigs(oc.Namespace()).Patch(dcName,
764+
types.StrategicMergePatchType, []byte(`{"spec": {"paused": true}}`))
765+
o.Expect(err).NotTo(o.HaveOccurred())
766+
767+
_, err = waitForDCModification(oc, dc.Namespace, dc.Name, deploymentChangeTimeout,
768+
dc.GetResourceVersion(), func(config *appsapi.DeploymentConfig) (bool, error) {
769+
if config.Status.ObservedGeneration >= dc.Generation {
770+
return true, nil
771+
}
772+
return false, nil
773+
})
774+
o.Expect(err).NotTo(o.HaveOccurred(), fmt.Sprintf("failed to wait on generation >= %d to be observed by DC %s/%s", dc.Generation, dc.Namespace, dc.Name))
759775
})
760776
})
761777

0 commit comments

Comments
 (0)