Skip to content

Commit 5e2cc59

Browse files
committed
Never deploy unspecified images in DC
1 parent b888212 commit 5e2cc59

File tree

3 files changed

+120
-48
lines changed

3 files changed

+120
-48
lines changed

pkg/apps/controller/deploymentconfig/deploymentconfig_controller.go

+73-44
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,42 +139,56 @@ func (c *DeploymentConfigController) Handle(config *appsapi.DeploymentConfig) er
128139
return err
129140
}
130141
}
131-
// Process triggers and start an initial rollouts
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+
132151
configCopy, err := appsutil.DeploymentConfigDeepCopy(config)
133152
if err != nil {
134153
glog.Errorf("Unable to copy deployment config: %v", err)
135-
return c.updateStatus(config, existingDeployments)
154+
return c.updateStatus(config, existingDeployments, false)
136155
}
137-
shouldTrigger, shouldSkip := triggerActivated(configCopy, latestExists, latestDeployment, c.codec)
138-
if !shouldSkip && shouldTrigger {
139-
configCopy.Status.LatestVersion++
140-
return c.updateStatus(configCopy, existingDeployments)
156+
// Process triggers and start an initial rollouts
157+
shouldTrigger, shouldSkip, err := triggerActivated(configCopy, latestExists, latestDeployment, c.codec)
158+
if err != nil {
159+
return fmt.Errorf("triggerActivated failed: %v", err)
141160
}
142-
// Have to wait for the image trigger to get the image before proceeding.
143-
if shouldSkip && appsutil.IsInitialDeployment(config) {
144-
return c.updateStatus(configCopy, existingDeployments)
161+
162+
if shouldSkip {
163+
return c.updateStatus(configCopy, existingDeployments, true)
164+
}
165+
166+
if shouldTrigger {
167+
configCopy.Status.LatestVersion++
168+
_, err := c.dn.DeploymentConfigs(configCopy.Namespace).UpdateStatus(configCopy)
169+
return err
145170
}
171+
146172
// If the latest deployment already exists, reconcile existing deployments
147173
// and return early.
148174
if latestExists {
149175
// If the latest deployment is still running, try again later. We don't
150176
// want to compete with the deployer.
151177
if !appsutil.IsTerminatedDeployment(latestDeployment) {
152-
return c.updateStatus(config, existingDeployments)
178+
return c.updateStatus(config, existingDeployments, false)
153179
}
154180

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

165-
return c.updateStatus(config, existingDeployments)
184+
// Never deploy with invalid or unresolved images
185+
for i, container := range config.Spec.Template.Spec.Containers {
186+
if len(strings.TrimSpace(container.Image)) == 0 {
187+
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)
188+
return c.updateStatus(config, existingDeployments, true)
189+
}
166190
}
191+
167192
// No deployments are running and the latest deployment doesn't exist, so
168193
// create the new deployment.
169194
deployment, err := appsutil.MakeDeploymentV1(config, c.codec)
@@ -186,17 +211,17 @@ func (c *DeploymentConfigController) Handle(config *appsapi.DeploymentConfig) er
186211
if isOurs {
187212
// If the deployment was already created, just move on. The cache could be
188213
// stale, or another process could have already handled this update.
189-
return c.updateStatus(config, existingDeployments)
214+
return c.updateStatus(config, existingDeployments, true)
190215
} else {
191-
err = fmt.Errorf("replication controller %s already exists and deployment config is not allowed to claim it.", deployment.Name)
216+
err = fmt.Errorf("replication controller %s already exists and deployment config is not allowed to claim it", deployment.Name)
192217
c.recorder.Eventf(config, v1.EventTypeWarning, "DeploymentCreationFailed", "Couldn't deploy version %d: %v", config.Status.LatestVersion, err)
193-
return c.updateStatus(config, existingDeployments)
218+
return c.updateStatus(config, existingDeployments, true)
194219
}
195220
}
196221
c.recorder.Eventf(config, v1.EventTypeWarning, "DeploymentCreationFailed", "Couldn't deploy version %d: %s", config.Status.LatestVersion, err)
197222
// We don't care about this error since we need to report the create failure.
198223
cond := appsutil.NewDeploymentCondition(appsapi.DeploymentProgressing, kapi.ConditionFalse, appsapi.FailedRcCreateReason, err.Error())
199-
_ = c.updateStatus(config, existingDeployments, *cond)
224+
_ = c.updateStatus(config, existingDeployments, true, *cond)
200225
return fmt.Errorf("couldn't create deployment for deployment config %s: %v", appsutil.LabelForDeploymentConfig(config), err)
201226
}
202227
msg := fmt.Sprintf("Created new replication controller %q for version %d", created.Name, config.Status.LatestVersion)
@@ -210,7 +235,7 @@ func (c *DeploymentConfigController) Handle(config *appsapi.DeploymentConfig) er
210235
}
211236

212237
cond := appsutil.NewDeploymentCondition(appsapi.DeploymentProgressing, kapi.ConditionTrue, appsapi.NewReplicationControllerReason, msg)
213-
return c.updateStatus(config, existingDeployments, *cond)
238+
return c.updateStatus(config, existingDeployments, true, *cond)
214239
}
215240

216241
// reconcileDeployments reconciles existing deployment replica counts which
@@ -292,13 +317,13 @@ func (c *DeploymentConfigController) reconcileDeployments(existingDeployments []
292317
c.recorder.Eventf(config, v1.EventTypeWarning, "ReplicationControllerCleanupFailed", "Couldn't clean up replication controllers: %v", err)
293318
}
294319

295-
return c.updateStatus(config, updatedDeployments)
320+
return c.updateStatus(config, updatedDeployments, true)
296321
}
297322

298323
// Update the status of the provided deployment config. Additional conditions will override any other condition in the
299324
// deployment config status.
300-
func (c *DeploymentConfigController) updateStatus(config *appsapi.DeploymentConfig, deployments []*v1.ReplicationController, additional ...appsapi.DeploymentCondition) error {
301-
newStatus := calculateStatus(config, deployments, additional...)
325+
func (c *DeploymentConfigController) updateStatus(config *appsapi.DeploymentConfig, deployments []*v1.ReplicationController, updateObservedGeneration bool, additional ...appsapi.DeploymentCondition) error {
326+
newStatus := calculateStatus(config, deployments, updateObservedGeneration, additional...)
302327

303328
// NOTE: We should update the status of the deployment config only if we need to, otherwise
304329
// we hotloop between updates.
@@ -390,7 +415,7 @@ func (c *DeploymentConfigController) cancelRunningRollouts(config *appsapi.Deplo
390415
return nil
391416
}
392417

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

436+
generation := config.Status.ObservedGeneration
437+
if updateObservedGeneration {
438+
generation = config.Generation
439+
}
440+
411441
status := appsapi.DeploymentConfigStatus{
412442
LatestVersion: config.Status.LatestVersion,
413443
Details: config.Status.Details,
414-
ObservedGeneration: config.Generation,
444+
ObservedGeneration: generation,
415445
Replicas: appsutil.GetStatusReplicaCountForDeployments(rcs),
416446
UpdatedReplicas: latestReplicas,
417447
AvailableReplicas: available,
@@ -533,17 +563,17 @@ func (c *DeploymentConfigController) cleanupOldDeployments(existingDeployments [
533563
// triggers were activated (config change or image change). The first bool indicates that
534564
// the triggers are active and second indicates if we should skip the rollout because we
535565
// are waiting for the trigger to complete update (waiting for image for example).
536-
func triggerActivated(config *appsapi.DeploymentConfig, latestExists bool, latestDeployment *v1.ReplicationController, codec runtime.Codec) (bool, bool) {
566+
func triggerActivated(config *appsapi.DeploymentConfig, latestExists bool, latestDeployment *v1.ReplicationController, codec runtime.Codec) (bool, bool, error) {
537567
if config.Spec.Paused {
538-
return false, false
568+
return false, false, nil
539569
}
540570
imageTrigger := appsutil.HasImageChangeTrigger(config)
541571
configTrigger := appsutil.HasChangeTrigger(config)
542572
hasTrigger := imageTrigger || configTrigger
543573

544574
// no-op when no triggers are defined.
545575
if !hasTrigger {
546-
return false, false
576+
return false, false, nil
547577
}
548578

549579
// Handle initial rollouts
@@ -556,50 +586,49 @@ func triggerActivated(config *appsapi.DeploymentConfig, latestExists bool, lates
556586
// TODO: Technically this is not a config change cause, but we will have to report the image that caused the trigger.
557587
// In some cases it might be difficult because config can have multiple ICT.
558588
appsutil.RecordConfigChangeCause(config)
559-
return true, false
589+
return true, false, nil
560590
}
561591
glog.V(4).Infof("Rolling out initial deployment for %s/%s deferred until its images are ready", config.Namespace, config.Name)
562-
return false, true
592+
return false, true, nil
563593
}
564594
// Rollout if we only have config change trigger.
565595
if configTrigger {
566596
glog.V(4).Infof("Rolling out initial deployment for %s/%s", config.Namespace, config.Name)
567597
appsutil.RecordConfigChangeCause(config)
568-
return true, false
598+
return true, false, nil
569599
}
570600
// We are waiting for the initial RC to be created.
571-
return false, false
601+
return false, false, nil
572602
}
573603

574604
// Wait for the RC to be created
575605
if !latestExists {
576-
return false, true
606+
return false, false, nil
577607
}
578608

579609
// We need existing deployment at this point to compare its template with current config template.
580610
if latestDeployment == nil {
581-
return false, false
611+
return false, false, nil
582612
}
583613

584614
if imageTrigger {
585615
if ok, imageNames := appsutil.HasUpdatedImages(config, latestDeployment); ok {
586616
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, ","))
587617
appsutil.RecordImageChangeCauses(config, imageNames)
588-
return true, false
618+
return true, false, nil
589619
}
590620
}
591621

592622
if configTrigger {
593623
isLatest, changes, err := appsutil.HasLatestPodTemplate(config, latestDeployment, codec)
594624
if err != nil {
595-
glog.Errorf("Error while checking for latest pod template in replication controller: %v", err)
596-
return false, true
625+
return false, false, fmt.Errorf("error while checking for latest pod template in replication controller: %v", err)
597626
}
598627
if !isLatest {
599628
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)
600629
appsutil.RecordConfigChangeCause(config)
601-
return true, false
630+
return true, false, nil
602631
}
603632
}
604-
return false, false
633+
return false, false, nil
605634
}

pkg/apps/controller/deploymentconfig/deploymentconfig_controller_test.go

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

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

504-
dc *appsapi.DeploymentConfig
505-
rcs []*v1.ReplicationController
507+
dc *appsapi.DeploymentConfig
508+
rcs []*v1.ReplicationController
509+
updateObservedGeneration bool
506510

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

@@ -551,7 +578,7 @@ func TestCalculateStatus(t *testing.T) {
551578
}
552579

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

test/extended/deployments/deployments.go

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

665665
g.Describe("paused [Conformance]", func() {
666+
dcName := "paused"
666667
g.AfterEach(func() {
667-
failureTrap(oc, "paused", g.CurrentGinkgoTestDescription().Failed)
668+
failureTrap(oc, dcName, g.CurrentGinkgoTestDescription().Failed)
668669
})
669670

670671
g.It("should disable actions on deployments", func() {
671672
resource, name, err := createFixture(oc, pausedDeploymentFixture)
672673
o.Expect(err).NotTo(o.HaveOccurred())
674+
o.Expect(name).To(o.Equal(dcName))
673675

674676
_, rcs, _, err := deploymentInfo(oc, name)
675677
o.Expect(err).NotTo(o.HaveOccurred())
@@ -719,6 +721,20 @@ var _ = g.Describe("[Feature:DeploymentConfig] deploymentconfigs", func() {
719721
})
720722
o.Expect(err).NotTo(o.HaveOccurred())
721723
o.Expect(waitForLatestCondition(oc, name, deploymentRunTimeout, deploymentReachedCompletion)).NotTo(o.HaveOccurred())
724+
725+
g.By("making sure it updates observedGeneration after being paused")
726+
dc, err := oc.AppsClient().Apps().DeploymentConfigs(oc.Namespace()).Patch(dcName,
727+
types.StrategicMergePatchType, []byte(`{"spec": {"paused": true}}`))
728+
o.Expect(err).NotTo(o.HaveOccurred())
729+
730+
_, err = waitForDCModification(oc, dc.Namespace, dc.Name, deploymentChangeTimeout,
731+
dc.GetResourceVersion(), func(config *appsapi.DeploymentConfig) (bool, error) {
732+
if config.Status.ObservedGeneration >= dc.Generation {
733+
return true, nil
734+
}
735+
return false, nil
736+
})
737+
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))
722738
})
723739
})
724740

0 commit comments

Comments
 (0)