Skip to content

Commit d3cdfec

Browse files
Merge pull request #17498 from csrwng/fix_build_controller
Automatic merge from submit-queue. build controller: fix start of serial builds on cancellation/deletion Current code will only retry a buildconfig if there's a running build *and* there's no pending builds waiting to be started. The conditions should be independent (*or*ed). If there's no pending builds it may be that the cache hasn't caught up. This fix separates the conditions and logs different messages for each. When an active build is deleted, we should also poke the buildconfig to start the next build. Fixes #17353
2 parents 51d3ec1 + 95b3459 commit d3cdfec

File tree

2 files changed

+82
-1
lines changed

2 files changed

+82
-1
lines changed

pkg/build/controller/build/build_controller.go

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,7 @@ func NewBuildController(params *BuildControllerParams) *BuildController {
213213
c.buildInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
214214
AddFunc: c.buildAdded,
215215
UpdateFunc: c.buildUpdated,
216+
DeleteFunc: c.buildDeleted,
216217
})
217218
params.ImageStreamInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
218219
AddFunc: c.imageStreamAdded,
@@ -1067,10 +1068,14 @@ func (bc *BuildController) handleBuildConfig(bcNamespace string, bcName string)
10671068
return err
10681069
}
10691070
glog.V(5).Infof("Build config %s/%s: has %d next builds, is running builds: %v", bcNamespace, bcName, len(nextBuilds), hasRunningBuilds)
1070-
if len(nextBuilds) == 0 && hasRunningBuilds {
1071+
if hasRunningBuilds {
10711072
glog.V(4).Infof("Build config %s/%s has running builds, will retry", bcNamespace, bcName)
10721073
return fmt.Errorf("build config %s/%s has running builds and cannot run more builds", bcNamespace, bcName)
10731074
}
1075+
if len(nextBuilds) == 0 {
1076+
glog.V(4).Infof("Build config %s/%s has no builds to run next, will retry", bcNamespace, bcName)
1077+
return fmt.Errorf("build config %s/%s has no builds to run next", bcNamespace, bcName)
1078+
}
10741079

10751080
// Enqueue any builds to build next
10761081
for _, build := range nextBuilds {
@@ -1174,6 +1179,29 @@ func (bc *BuildController) buildUpdated(old, cur interface{}) {
11741179
bc.enqueueBuild(build)
11751180
}
11761181

1182+
// buildDeleted is called by the build informer event handler whenever a build
1183+
// is deleted
1184+
func (bc *BuildController) buildDeleted(obj interface{}) {
1185+
build, ok := obj.(*buildapi.Build)
1186+
if !ok {
1187+
tombstone, ok := obj.(cache.DeletedFinalStateUnknown)
1188+
if !ok {
1189+
utilruntime.HandleError(fmt.Errorf("couldn't get object from tombstone: %+v", obj))
1190+
return
1191+
}
1192+
build, ok = tombstone.Obj.(*buildapi.Build)
1193+
if !ok {
1194+
utilruntime.HandleError(fmt.Errorf("tombstone contained object that is not a pod: %+v", obj))
1195+
return
1196+
}
1197+
}
1198+
// If the build was not in a complete state, poke the buildconfig to run the next build
1199+
if !buildutil.IsBuildComplete(build) {
1200+
bcName := buildutil.ConfigNameForBuild(build)
1201+
bc.enqueueBuildConfig(build.Namespace, bcName)
1202+
}
1203+
}
1204+
11771205
// enqueueBuild adds the given build to the buildQueue.
11781206
func (bc *BuildController) enqueueBuild(build *buildapi.Build) {
11791207
key := resourceName(build.Namespace, build.Name)

test/extended/builds/run_policy.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package builds
22

33
import (
4+
"fmt"
45
"strings"
56
"time"
67

@@ -310,6 +311,58 @@ var _ = g.Describe("[Feature:Builds][Slow] using build configuration runPolicy",
310311
})
311312
})
312313

314+
g.Describe("build configuration with Serial build run policy handling deletion", func() {
315+
g.It("starts the next build immediately after running one is deleted", func() {
316+
g.By("starting multiple builds")
317+
318+
bcName := "sample-serial-build"
319+
for i := 0; i < 3; i++ {
320+
_, _, err := exutil.StartBuild(oc, bcName)
321+
o.Expect(err).NotTo(o.HaveOccurred())
322+
}
323+
324+
buildWatch, err := oc.BuildClient().Build().Builds(oc.Namespace()).Watch(metav1.ListOptions{
325+
LabelSelector: buildutil.BuildConfigSelector(bcName).String(),
326+
})
327+
defer buildWatch.Stop()
328+
o.Expect(err).NotTo(o.HaveOccurred())
329+
330+
var deleteTime, deleteTime2 time.Time
331+
done := false
332+
var timedOut error
333+
for !done {
334+
select {
335+
case event := <-buildWatch.ResultChan():
336+
build := event.Object.(*buildapi.Build)
337+
if build.Status.Phase == buildapi.BuildPhasePending {
338+
if build.Name == "sample-serial-build-1" {
339+
err := oc.Run("delete").Args("build", "sample-serial-build-1", "--ignore-not-found").Execute()
340+
o.Expect(err).ToNot(o.HaveOccurred())
341+
deleteTime = time.Now()
342+
}
343+
if build.Name == "sample-serial-build-2" {
344+
duration := time.Now().Sub(deleteTime)
345+
o.Expect(duration).To(o.BeNumerically("<", 20*time.Second), "next build should have started less than 20s after deleted build")
346+
err := oc.Run("delete").Args("build", "sample-serial-build-2", "--ignore-not-found").Execute()
347+
o.Expect(err).ToNot(o.HaveOccurred())
348+
deleteTime2 = time.Now()
349+
}
350+
if build.Name == "sample-serial-build-3" {
351+
duration := time.Now().Sub(deleteTime2)
352+
o.Expect(duration).To(o.BeNumerically("<", 20*time.Second), "next build should have started less than 20s after deleted build")
353+
done = true
354+
}
355+
}
356+
case <-time.After(2 * time.Minute):
357+
// Give up waiting and finish after 2 minutes
358+
timedOut = fmt.Errorf("timed out waiting for pending build")
359+
done = true
360+
}
361+
}
362+
o.Expect(timedOut).NotTo(o.HaveOccurred())
363+
})
364+
})
365+
313366
g.Describe("build configuration with SerialLatestOnly build run policy", func() {
314367
g.It("runs the builds in serial order but cancel previous builds", func() {
315368
g.By("starting multiple builds")

0 commit comments

Comments
 (0)