Skip to content

Commit 38133a8

Browse files
committed
1 parent 9983952 commit 38133a8

File tree

8 files changed

+186
-33
lines changed

8 files changed

+186
-33
lines changed

pkg/build/controller/buildpod/controller.go

+26-6
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,9 @@ func (bc *BuildPodController) HandlePod(pod *kapi.Pod) error {
128128
nextStatus := build.Status.Phase
129129
currentReason := build.Status.Reason
130130

131+
// if the build is marked failed, the build status reason has already been
132+
// set (probably by the build pod itself), so don't do any updating here
133+
// or we'll overwrite the correct value.
131134
if build.Status.Phase != buildapi.BuildPhaseFailed {
132135
switch pod.Status.Phase {
133136
case kapi.PodPending:
@@ -183,9 +186,14 @@ func (bc *BuildPodController) HandlePod(pod *kapi.Pod) error {
183186
build.Status.Message = ""
184187
}
185188
}
189+
190+
needsUpdate := false
186191
// Update the build object when it progress to a next state or the reason for
187-
// the current state changed.
188-
if (!common.HasBuildPodNameAnnotation(build) || build.Status.Phase != nextStatus || build.Status.Phase == buildapi.BuildPhaseFailed) || !buildutil.IsBuildComplete(build) {
192+
// the current state changed. Do not touch builds that were marked failed
193+
// because we'd potentially be overwriting build state information set by the
194+
// build pod directly.
195+
if (!common.HasBuildPodNameAnnotation(build) || build.Status.Phase != nextStatus) && !buildutil.IsBuildComplete(build) {
196+
needsUpdate = true
189197
common.SetBuildPodNameAnnotation(build, pod.Name)
190198
reason := ""
191199
if len(build.Status.Reason) > 0 {
@@ -208,15 +216,27 @@ func (bc *BuildPodController) HandlePod(pod *kapi.Pod) error {
208216
build.Status.StartTimestamp = &now
209217
bc.recorder.Eventf(build, kapi.EventTypeNormal, buildapi.BuildRunningEventReason, fmt.Sprintf(buildapi.BuildRunningEventMessage, build.Namespace, build.Name))
210218
}
219+
}
220+
}
221+
222+
// we're going to get pod relist events for completed/failed pods,
223+
// there's no reason to re-update the build and rerun
224+
// HandleBuildCompletion if we've already done it for this
225+
// build previously.
226+
buildWasComplete := build.Status.CompletionTimestamp != nil
227+
if !buildWasComplete && buildutil.IsBuildComplete(build) && build.Status.Phase != buildapi.BuildPhaseCancelled {
228+
needsUpdate = common.SetBuildCompletionTimeAndDuration(build)
229+
}
230+
if needsUpdate {
211231
if err := bc.buildUpdater.Update(build.Namespace, build); err != nil {
212232
return fmt.Errorf("failed to update build %s/%s: %v", build.Namespace, build.Name, err)
213233
}
214234
glog.V(4).Infof("Build %s/%s status was updated to %s", build.Namespace, build.Name, build.Status.Phase)
215-
216-
if buildutil.IsBuildComplete(build) {
217-
common.HandleBuildCompletion(build, bc.runPolicies)
218-
}
219235
}
236+
if !buildWasComplete && buildutil.IsBuildComplete(build) {
237+
common.HandleBuildCompletion(build, bc.runPolicies)
238+
}
239+
220240
return nil
221241
}
222242

pkg/build/controller/buildpod/controller_test.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ func TestHandlePod(t *testing.T) {
158158
outStatus: buildapi.BuildPhaseComplete,
159159
podStatus: kapi.PodSucceeded,
160160
exitCode: 0,
161-
startTimestamp: nil,
161+
startTimestamp: curtime,
162162
completionTimestamp: curtime,
163163
},
164164
{ // 4
@@ -167,7 +167,7 @@ func TestHandlePod(t *testing.T) {
167167
outStatus: buildapi.BuildPhaseFailed,
168168
podStatus: kapi.PodFailed,
169169
exitCode: -1,
170-
startTimestamp: nil,
170+
startTimestamp: curtime,
171171
completionTimestamp: curtime,
172172
},
173173
{ // 5
@@ -177,7 +177,7 @@ func TestHandlePod(t *testing.T) {
177177
podStatus: kapi.PodSucceeded,
178178
exitCode: 0,
179179
buildUpdater: &errBuildUpdater{},
180-
startTimestamp: nil,
180+
startTimestamp: curtime,
181181
completionTimestamp: curtime,
182182
},
183183
{ // 6

pkg/build/controller/common/util.go

+14-3
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,23 @@ import (
1111
utilruntime "k8s.io/kubernetes/pkg/util/runtime"
1212
)
1313

14-
func SetBuildCompletionTimeAndDuration(build *buildapi.Build) {
14+
// SetBuildCompletionTimeAndDuration will set the build completion timestamp
15+
// to the current time if it is nil. It will also set the start timestamp to
16+
// the same value if it is nil. Returns true if the build object was
17+
// modified.
18+
func SetBuildCompletionTimeAndDuration(build *buildapi.Build) bool {
19+
if build.Status.CompletionTimestamp != nil {
20+
return false
21+
}
1522
now := unversioned.Now()
1623
build.Status.CompletionTimestamp = &now
17-
if build.Status.StartTimestamp != nil {
18-
build.Status.Duration = build.Status.CompletionTimestamp.Rfc3339Copy().Time.Sub(build.Status.StartTimestamp.Rfc3339Copy().Time)
24+
// apparently this build completed so fast we didn't see the pod running event,
25+
// so just use the completion time as the start time.
26+
if build.Status.StartTimestamp == nil {
27+
build.Status.StartTimestamp = &now
1928
}
29+
build.Status.Duration = build.Status.CompletionTimestamp.Rfc3339Copy().Time.Sub(build.Status.StartTimestamp.Rfc3339Copy().Time)
30+
return true
2031
}
2132

2233
func HandleBuildCompletion(build *buildapi.Build, runPolicies []policy.RunPolicy) {

pkg/build/controller/controller_test.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -447,23 +447,23 @@ func TestCancelBuild(t *testing.T) {
447447
inStatus: buildapi.BuildPhaseNew,
448448
outStatus: buildapi.BuildPhaseCancelled,
449449
exitCode: 0,
450-
startTimestamp: nil,
450+
startTimestamp: curtime,
451451
completionTimestamp: curtime,
452452
},
453453
{ // 1
454454
inStatus: buildapi.BuildPhasePending,
455455
outStatus: buildapi.BuildPhaseCancelled,
456456
podStatus: kapi.PodRunning,
457457
exitCode: 0,
458-
startTimestamp: nil,
458+
startTimestamp: curtime,
459459
completionTimestamp: curtime,
460460
},
461461
{ // 2
462462
inStatus: buildapi.BuildPhaseRunning,
463463
outStatus: buildapi.BuildPhaseCancelled,
464464
podStatus: kapi.PodRunning,
465465
exitCode: 0,
466-
startTimestamp: nil,
466+
startTimestamp: curtime,
467467
completionTimestamp: curtime,
468468
},
469469
{ // 3

pkg/build/controller/policy/policy.go

+14-10
Original file line numberDiff line numberDiff line change
@@ -142,17 +142,21 @@ func handleComplete(lister buildclient.BuildLister, updater buildclient.BuildUpd
142142
}
143143
for _, build := range nextBuilds {
144144
// TODO: replace with informer notification requeueing in the future
145-
build.Annotations[buildapi.BuildAcceptedAnnotation] = uuid.NewRandom().String()
146-
err := wait.Poll(500*time.Millisecond, 5*time.Second, func() (bool, error) {
147-
err := updater.Update(build.Namespace, build)
148-
if err != nil && errors.IsConflict(err) {
149-
glog.V(5).Infof("Error updating build %s/%s: %v (will retry)", build.Namespace, build.Name, err)
150-
return false, nil
145+
146+
// only set the annotation once.
147+
if _, ok := build.Annotations[buildapi.BuildAcceptedAnnotation]; !ok {
148+
build.Annotations[buildapi.BuildAcceptedAnnotation] = uuid.NewRandom().String()
149+
err := wait.Poll(500*time.Millisecond, 5*time.Second, func() (bool, error) {
150+
err := updater.Update(build.Namespace, build)
151+
if err != nil && errors.IsConflict(err) {
152+
glog.V(5).Infof("Error updating build %s/%s: %v (will retry)", build.Namespace, build.Name, err)
153+
return false, nil
154+
}
155+
return true, err
156+
})
157+
if err != nil {
158+
return err
151159
}
152-
return true, err
153-
})
154-
if err != nil {
155-
return err
156160
}
157161
}
158162
return nil

test/extended/builds/run_policy.go

+84
Original file line numberDiff line numberDiff line change
@@ -132,10 +132,21 @@ var _ = g.Describe("[builds][Slow] using build configuration runPolicy", func()
132132
defer buildWatch.Stop()
133133
o.Expect(err).NotTo(o.HaveOccurred())
134134

135+
sawCompletion := false
135136
for {
136137
event := <-buildWatch.ResultChan()
137138
build := event.Object.(*buildapi.Build)
139+
var lastCompletion time.Time
140+
if build.Status.Phase == buildapi.BuildPhaseComplete {
141+
lastCompletion = time.Now()
142+
o.Expect(build.Status.StartTimestamp).ToNot(o.BeNil(), "completed builds should have a valid start time")
143+
o.Expect(build.Status.CompletionTimestamp).ToNot(o.BeNil(), "completed builds should have a valid completion time")
144+
sawCompletion = true
145+
}
138146
if build.Status.Phase == buildapi.BuildPhaseRunning {
147+
latency := lastCompletion.Sub(time.Now())
148+
o.Expect(latency).To(o.BeNumerically("<", 10*time.Second), "next build should have started less than 10s after last completed build")
149+
139150
// Ignore events from complete builds (if there are any) if we already
140151
// verified the build.
141152
if _, exists := buildVerified[build.Name]; exists {
@@ -166,6 +177,7 @@ var _ = g.Describe("[builds][Slow] using build configuration runPolicy", func()
166177
break
167178
}
168179
}
180+
o.Expect(sawCompletion).To(o.BeTrue(), "should have seen at least one build complete")
169181
})
170182
})
171183

@@ -212,6 +224,78 @@ var _ = g.Describe("[builds][Slow] using build configuration runPolicy", func()
212224
})
213225
})
214226

227+
g.Describe("build configuration with Serial build run policy handling failure", func() {
228+
g.It("starts the next build immediately after one fails", func() {
229+
g.By("starting multiple builds")
230+
bcName := "sample-serial-build-fail"
231+
232+
for i := 0; i < 3; i++ {
233+
_, _, err := exutil.StartBuild(oc, bcName)
234+
o.Expect(err).NotTo(o.HaveOccurred())
235+
}
236+
237+
buildWatch, err := oc.Client().Builds(oc.Namespace()).Watch(kapi.ListOptions{
238+
LabelSelector: buildutil.BuildConfigSelector(bcName),
239+
})
240+
defer buildWatch.Stop()
241+
o.Expect(err).NotTo(o.HaveOccurred())
242+
243+
var failTime, failTime2 time.Time
244+
done, timestamps1, timestamps2, timestamps3 := false, false, false, false
245+
246+
for done == false {
247+
select {
248+
case event := <-buildWatch.ResultChan():
249+
build := event.Object.(*buildapi.Build)
250+
if build.Status.Phase == buildapi.BuildPhaseFailed {
251+
if build.Name == "sample-serial-build-fail-1" {
252+
// this may not be set on the first build modified to failed event because
253+
// the build gets marked failed by the build pod, but the timestamps get
254+
// set by the buildpod controller.
255+
if build.Status.CompletionTimestamp != nil {
256+
o.Expect(build.Status.StartTimestamp).ToNot(o.BeNil(), "failed builds should have a valid start time")
257+
o.Expect(build.Status.CompletionTimestamp).ToNot(o.BeNil(), "failed builds should have a valid completion time")
258+
timestamps1 = true
259+
}
260+
failTime = time.Now()
261+
}
262+
if build.Name == "sample-serial-build-fail-2" {
263+
duration := time.Now().Sub(failTime)
264+
o.Expect(duration).To(o.BeNumerically("<", 10*time.Second), "next build should have started less than 10s after failed build")
265+
if build.Status.CompletionTimestamp != nil {
266+
o.Expect(build.Status.StartTimestamp).ToNot(o.BeNil(), "failed builds should have a valid start time")
267+
o.Expect(build.Status.CompletionTimestamp).ToNot(o.BeNil(), "failed builds should have a valid completion time")
268+
timestamps2 = true
269+
}
270+
failTime2 = time.Now()
271+
}
272+
if build.Name == "sample-serial-build-fail-3" {
273+
duration := time.Now().Sub(failTime2)
274+
o.Expect(duration).To(o.BeNumerically("<", 10*time.Second), "next build should have started less than 10s after failed build")
275+
if build.Status.CompletionTimestamp != nil {
276+
o.Expect(build.Status.StartTimestamp).ToNot(o.BeNil(), "failed builds should have a valid start time")
277+
o.Expect(build.Status.CompletionTimestamp).ToNot(o.BeNil(), "failed builds should have a valid completion time")
278+
timestamps3 = true
279+
}
280+
}
281+
}
282+
// once we have all the expected timestamps, or we run out of time, we can bail.
283+
if timestamps1 && timestamps2 && timestamps3 {
284+
done = true
285+
}
286+
case <-time.After(2 * time.Minute):
287+
// we've waited as long as we dare, go see if we got all the timestamp data we expected.
288+
// if we have the timestamp data, we also know that we checked the next build start latency.
289+
done = true
290+
}
291+
}
292+
o.Expect(timestamps1).To(o.BeTrue(), "failed builds should have start and completion timestamps set")
293+
o.Expect(timestamps2).To(o.BeTrue(), "failed builds should have start and completion timestamps set")
294+
o.Expect(timestamps3).To(o.BeTrue(), "failed builds should have start and completion timestamps set")
295+
296+
})
297+
})
298+
215299
g.Describe("build configuration with SerialLatestOnly build run policy", func() {
216300
g.It("runs the builds in serial order but cancel previous builds", func() {
217301
g.By("starting multiple builds")

test/extended/testdata/bindata.go

+21-4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/extended/testdata/run_policy/serial-bc.yaml

+21-4
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,24 @@
2424
from:
2525
kind: "DockerImage"
2626
name: "centos/ruby-22-centos7"
27-
resources: {}
28-
status:
29-
lastVersion: 0
30-
27+
-
28+
kind: "BuildConfig"
29+
apiVersion: "v1"
30+
metadata:
31+
name: "sample-serial-build-fail"
32+
spec:
33+
runPolicy: "Serial"
34+
triggers:
35+
-
36+
type: "imageChange"
37+
imageChange: {}
38+
source:
39+
type: "Git"
40+
git:
41+
uri: "git://github.com/openshift/invalidrepo.git"
42+
strategy:
43+
type: "Source"
44+
sourceStrategy:
45+
from:
46+
kind: "DockerImage"
47+
name: "centos/ruby-22-centos7"

0 commit comments

Comments
 (0)