Skip to content

Commit 7c91f63

Browse files
author
OpenShift Bot
authored
Merge pull request #13510 from csrwng/build_shared_informer
Merged by openshift-bot
2 parents 6a175fa + 18036b7 commit 7c91f63

File tree

11 files changed

+1049
-629
lines changed

11 files changed

+1049
-629
lines changed

pkg/build/controller/buildpod/controller.go

+461
Large diffs are not rendered by default.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,399 @@
1+
package buildpod
2+
3+
import (
4+
"errors"
5+
"reflect"
6+
"testing"
7+
"time"
8+
9+
kapi "k8s.io/kubernetes/pkg/api"
10+
"k8s.io/kubernetes/pkg/api/unversioned"
11+
"k8s.io/kubernetes/pkg/client/cache"
12+
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake"
13+
14+
buildapi "github.com/openshift/origin/pkg/build/api"
15+
buildclient "github.com/openshift/origin/pkg/build/client"
16+
"github.com/openshift/origin/pkg/build/controller/common"
17+
"github.com/openshift/origin/pkg/client/testclient"
18+
)
19+
20+
type errBuildUpdater struct{}
21+
22+
func (ec *errBuildUpdater) Update(namespace string, build *buildapi.Build) error {
23+
return errors.New("UpdateBuild error!")
24+
}
25+
26+
type customBuildUpdater struct {
27+
UpdateFunc func(namespace string, build *buildapi.Build) error
28+
}
29+
30+
func (c *customBuildUpdater) Update(namespace string, build *buildapi.Build) error {
31+
return c.UpdateFunc(namespace, build)
32+
}
33+
34+
func mockPod(status kapi.PodPhase, exitCode int) *kapi.Pod {
35+
return &kapi.Pod{
36+
ObjectMeta: kapi.ObjectMeta{
37+
Name: "data-build-build",
38+
Namespace: "namespace",
39+
Annotations: map[string]string{
40+
buildapi.BuildAnnotation: "data-build",
41+
},
42+
},
43+
Status: kapi.PodStatus{
44+
Phase: status,
45+
ContainerStatuses: []kapi.ContainerStatus{
46+
{
47+
State: kapi.ContainerState{
48+
Terminated: &kapi.ContainerStateTerminated{ExitCode: int32(exitCode)},
49+
},
50+
},
51+
},
52+
},
53+
}
54+
}
55+
56+
func mockBuild(phase buildapi.BuildPhase, output buildapi.BuildOutput) *buildapi.Build {
57+
return &buildapi.Build{
58+
ObjectMeta: kapi.ObjectMeta{
59+
Name: "data-build",
60+
Namespace: "namespace",
61+
Annotations: map[string]string{
62+
buildapi.BuildConfigAnnotation: "test-bc",
63+
},
64+
Labels: map[string]string{
65+
"name": "dataBuild",
66+
// TODO: Switch this test to use Serial policy
67+
buildapi.BuildRunPolicyLabel: string(buildapi.BuildRunPolicyParallel),
68+
buildapi.BuildConfigLabel: "test-bc",
69+
},
70+
},
71+
Spec: buildapi.BuildSpec{
72+
CommonSpec: buildapi.CommonSpec{
73+
Source: buildapi.BuildSource{
74+
Git: &buildapi.GitBuildSource{
75+
URI: "http://my.build.com/the/build/Dockerfile",
76+
},
77+
ContextDir: "contextimage",
78+
},
79+
Strategy: buildapi.BuildStrategy{
80+
DockerStrategy: &buildapi.DockerBuildStrategy{},
81+
},
82+
Output: output,
83+
},
84+
},
85+
Status: buildapi.BuildStatus{
86+
Phase: phase,
87+
},
88+
}
89+
}
90+
91+
type FakeIndexer struct {
92+
*cache.FakeCustomStore
93+
}
94+
95+
func mockBuildPodController(build *buildapi.Build, buildUpdater buildclient.BuildUpdater) *BuildPodController {
96+
buildInformer := cache.NewSharedIndexInformer(&cache.ListWatch{}, &buildapi.Build{}, 2*time.Minute, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc})
97+
podInformer := cache.NewSharedIndexInformer(&cache.ListWatch{}, &kapi.Pod{}, 2*time.Minute, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc})
98+
fakeSecret := &kapi.Secret{}
99+
fakeSecret.Name = "fakeSecret"
100+
fakeSecret.Namespace = "namespace"
101+
kclient := fake.NewSimpleClientset(fakeSecret)
102+
osclient := testclient.NewSimpleFake()
103+
c := NewBuildPodController(buildInformer, podInformer, kclient, osclient)
104+
if build != nil {
105+
c.buildStore.Indexer.Add(build)
106+
}
107+
if buildUpdater != nil {
108+
c.buildUpdater = buildUpdater
109+
}
110+
return c
111+
}
112+
113+
func TestHandlePod(t *testing.T) {
114+
type handlePodTest struct {
115+
matchID bool
116+
inStatus buildapi.BuildPhase
117+
outStatus buildapi.BuildPhase
118+
startTimestamp *unversioned.Time
119+
completionTimestamp *unversioned.Time
120+
podStatus kapi.PodPhase
121+
exitCode int
122+
buildUpdater buildclient.BuildUpdater
123+
}
124+
125+
dummy := unversioned.Now()
126+
curtime := &dummy
127+
tests := []handlePodTest{
128+
{ // 0
129+
matchID: false,
130+
inStatus: buildapi.BuildPhasePending,
131+
outStatus: buildapi.BuildPhasePending,
132+
podStatus: kapi.PodPending,
133+
exitCode: 0,
134+
startTimestamp: nil,
135+
completionTimestamp: nil,
136+
},
137+
{ // 1
138+
matchID: true,
139+
inStatus: buildapi.BuildPhasePending,
140+
outStatus: buildapi.BuildPhasePending,
141+
podStatus: kapi.PodPending,
142+
exitCode: 0,
143+
startTimestamp: nil,
144+
completionTimestamp: nil,
145+
},
146+
{ // 2
147+
matchID: true,
148+
inStatus: buildapi.BuildPhasePending,
149+
outStatus: buildapi.BuildPhaseRunning,
150+
podStatus: kapi.PodRunning,
151+
exitCode: 0,
152+
startTimestamp: curtime,
153+
completionTimestamp: nil,
154+
},
155+
{ // 3
156+
matchID: true,
157+
inStatus: buildapi.BuildPhaseRunning,
158+
outStatus: buildapi.BuildPhaseComplete,
159+
podStatus: kapi.PodSucceeded,
160+
exitCode: 0,
161+
startTimestamp: nil,
162+
completionTimestamp: curtime,
163+
},
164+
{ // 4
165+
matchID: true,
166+
inStatus: buildapi.BuildPhaseRunning,
167+
outStatus: buildapi.BuildPhaseFailed,
168+
podStatus: kapi.PodFailed,
169+
exitCode: -1,
170+
startTimestamp: nil,
171+
completionTimestamp: curtime,
172+
},
173+
{ // 5
174+
matchID: true,
175+
inStatus: buildapi.BuildPhaseRunning,
176+
outStatus: buildapi.BuildPhaseComplete,
177+
podStatus: kapi.PodSucceeded,
178+
exitCode: 0,
179+
buildUpdater: &errBuildUpdater{},
180+
startTimestamp: nil,
181+
completionTimestamp: curtime,
182+
},
183+
{ // 6
184+
matchID: true,
185+
inStatus: buildapi.BuildPhaseCancelled,
186+
outStatus: buildapi.BuildPhaseCancelled,
187+
podStatus: kapi.PodFailed,
188+
exitCode: 0,
189+
startTimestamp: nil,
190+
completionTimestamp: nil,
191+
},
192+
}
193+
194+
for i, tc := range tests {
195+
build := mockBuild(tc.inStatus, buildapi.BuildOutput{})
196+
// Default build updater to retrieve updated build
197+
if tc.buildUpdater == nil {
198+
tc.buildUpdater = &customBuildUpdater{
199+
UpdateFunc: func(namespace string, updatedBuild *buildapi.Build) error {
200+
build = updatedBuild
201+
return nil
202+
},
203+
}
204+
}
205+
ctrl := mockBuildPodController(build, tc.buildUpdater)
206+
pod := mockPod(tc.podStatus, tc.exitCode)
207+
if tc.matchID {
208+
build.Name = "name"
209+
}
210+
211+
err := ctrl.HandlePod(pod)
212+
213+
if tc.buildUpdater != nil && reflect.TypeOf(tc.buildUpdater).Elem().Name() == "errBuildUpdater" {
214+
if err == nil {
215+
t.Errorf("(%d) Expected error, got none", i)
216+
}
217+
// can't check tc.outStatus because the local build object does get updated
218+
// in this test (but would not updated in etcd)
219+
continue
220+
}
221+
if build.Status.Phase != tc.outStatus {
222+
t.Errorf("(%d) Expected %s, got %s!", i, tc.outStatus, build.Status.Phase)
223+
}
224+
if tc.inStatus != buildapi.BuildPhaseCancelled && tc.inStatus != buildapi.BuildPhaseComplete && !common.HasBuildPodNameAnnotation(build) {
225+
t.Errorf("(%d) Build does not have pod name annotation. %#v", i, build)
226+
}
227+
if tc.startTimestamp == nil && build.Status.StartTimestamp != nil {
228+
t.Errorf("(%d) Expected nil start timestamp, got %v!", i, build.Status.StartTimestamp)
229+
}
230+
if tc.startTimestamp != nil && build.Status.StartTimestamp == nil {
231+
t.Errorf("(%d) nil start timestamp!", i)
232+
}
233+
if tc.startTimestamp != nil && !tc.startTimestamp.Before(*build.Status.StartTimestamp) && tc.startTimestamp.Time != build.Status.StartTimestamp.Time {
234+
t.Errorf("(%d) Expected build start timestamp %v to be equal to or later than %v!", i, build.Status.StartTimestamp, tc.startTimestamp)
235+
}
236+
237+
if tc.completionTimestamp == nil && build.Status.CompletionTimestamp != nil {
238+
t.Errorf("(%d) Expected nil completion timestamp, got %v!", i, build.Status.CompletionTimestamp)
239+
}
240+
if tc.completionTimestamp != nil && build.Status.CompletionTimestamp == nil {
241+
t.Errorf("(%d) nil completion timestamp!", i)
242+
}
243+
if tc.completionTimestamp != nil && !tc.completionTimestamp.Before(*build.Status.CompletionTimestamp) && tc.completionTimestamp.Time != build.Status.CompletionTimestamp.Time {
244+
t.Errorf("(%d) Expected build completion timestamp %v to be equal to or later than %v!", i, build.Status.CompletionTimestamp, tc.completionTimestamp)
245+
}
246+
}
247+
}
248+
249+
func TestHandleBuildPodDeletionOK(t *testing.T) {
250+
updateWasCalled := false
251+
// only not finished build (buildutil.IsBuildComplete) should be handled
252+
build := mockBuild(buildapi.BuildPhaseRunning, buildapi.BuildOutput{})
253+
ctrl := mockBuildPodController(build, &customBuildUpdater{
254+
UpdateFunc: func(namespace string, build *buildapi.Build) error {
255+
updateWasCalled = true
256+
return nil
257+
},
258+
})
259+
pod := mockPod(kapi.PodSucceeded, 0)
260+
261+
err := ctrl.HandleBuildPodDeletion(pod)
262+
if err != nil {
263+
t.Errorf("Unexpected error %v", err)
264+
}
265+
if !updateWasCalled {
266+
t.Error("UpdateBuild was not called when it should have been!")
267+
}
268+
}
269+
270+
func TestHandlePipelineBuildPodDeletionOK(t *testing.T) {
271+
updateWasCalled := false
272+
// only not finished build (buildutil.IsBuildComplete) should be handled
273+
build := mockBuild(buildapi.BuildPhaseRunning, buildapi.BuildOutput{})
274+
build.Spec.Strategy.JenkinsPipelineStrategy = &buildapi.JenkinsPipelineBuildStrategy{}
275+
ctrl := mockBuildPodController(build, &customBuildUpdater{
276+
UpdateFunc: func(namespace string, build *buildapi.Build) error {
277+
updateWasCalled = true
278+
return nil
279+
},
280+
})
281+
pod := mockPod(kapi.PodSucceeded, 0)
282+
283+
err := ctrl.HandleBuildPodDeletion(pod)
284+
if err != nil {
285+
t.Errorf("Unexpected error %v", err)
286+
}
287+
if updateWasCalled {
288+
t.Error("UpdateBuild called when it should not have been!")
289+
}
290+
}
291+
292+
func TestHandleBuildPodDeletionOKFinishedBuild(t *testing.T) {
293+
updateWasCalled := false
294+
// finished build buildutil.IsBuildComplete should not be handled
295+
build := mockBuild(buildapi.BuildPhaseComplete, buildapi.BuildOutput{})
296+
ctrl := mockBuildPodController(build, &customBuildUpdater{
297+
UpdateFunc: func(namespace string, build *buildapi.Build) error {
298+
updateWasCalled = true
299+
return nil
300+
},
301+
})
302+
pod := mockPod(kapi.PodSucceeded, 0)
303+
304+
err := ctrl.HandleBuildPodDeletion(pod)
305+
if err != nil {
306+
t.Errorf("Unexpected error %v", err)
307+
}
308+
if updateWasCalled {
309+
t.Error("UpdateBuild was called when it should not!")
310+
}
311+
}
312+
313+
func TestHandleBuildPodDeletionOKErroneousBuild(t *testing.T) {
314+
updateWasCalled := false
315+
// erroneous builds should not be handled
316+
build := mockBuild(buildapi.BuildPhaseError, buildapi.BuildOutput{})
317+
ctrl := mockBuildPodController(build, &customBuildUpdater{
318+
UpdateFunc: func(namespace string, build *buildapi.Build) error {
319+
updateWasCalled = true
320+
return nil
321+
},
322+
})
323+
pod := mockPod(kapi.PodSucceeded, 0)
324+
325+
err := ctrl.HandleBuildPodDeletion(pod)
326+
if err != nil {
327+
t.Errorf("Unexpected error %v", err)
328+
}
329+
if updateWasCalled {
330+
t.Error("UpdateBuild was called when it should not!")
331+
}
332+
}
333+
334+
type fakeIndexer struct {
335+
*cache.FakeCustomStore
336+
}
337+
338+
func (*fakeIndexer) Index(indexName string, obj interface{}) ([]interface{}, error) { return nil, nil }
339+
func (*fakeIndexer) ListIndexFuncValues(indexName string) []string { return nil }
340+
func (*fakeIndexer) ByIndex(indexName, indexKey string) ([]interface{}, error) { return nil, nil }
341+
func (*fakeIndexer) GetIndexers() cache.Indexers { return nil }
342+
func (*fakeIndexer) AddIndexers(newIndexers cache.Indexers) error { return nil }
343+
344+
func newErrIndexer(err error) cache.Indexer {
345+
return &fakeIndexer{
346+
&cache.FakeCustomStore{
347+
GetByKeyFunc: func(key string) (interface{}, bool, error) {
348+
return nil, true, err
349+
},
350+
},
351+
}
352+
}
353+
354+
func TestHandleBuildPodDeletionBuildGetError(t *testing.T) {
355+
ctrl := mockBuildPodController(nil, &customBuildUpdater{})
356+
ctrl.buildStore.Indexer = newErrIndexer(errors.New("random"))
357+
pod := mockPod(kapi.PodSucceeded, 0)
358+
err := ctrl.HandleBuildPodDeletion(pod)
359+
if err == nil {
360+
t.Error("Expected random error, but got none!")
361+
}
362+
if err != nil && err.Error() != "random" {
363+
t.Errorf("Expected random error, got: %v", err)
364+
}
365+
}
366+
367+
func TestHandleBuildPodDeletionBuildNotExists(t *testing.T) {
368+
updateWasCalled := false
369+
ctrl := mockBuildPodController(nil, &customBuildUpdater{
370+
UpdateFunc: func(namespace string, build *buildapi.Build) error {
371+
updateWasCalled = true
372+
return nil
373+
},
374+
})
375+
pod := mockPod(kapi.PodSucceeded, 0)
376+
377+
err := ctrl.HandleBuildPodDeletion(pod)
378+
if err != nil {
379+
t.Errorf("Unexpected error %v", err)
380+
}
381+
if updateWasCalled {
382+
t.Error("UpdateBuild was called when it should not!")
383+
}
384+
}
385+
386+
func TestHandleBuildPodDeletionBuildUpdateError(t *testing.T) {
387+
build := mockBuild(buildapi.BuildPhaseRunning, buildapi.BuildOutput{})
388+
ctrl := mockBuildPodController(build, &customBuildUpdater{
389+
UpdateFunc: func(namespace string, build *buildapi.Build) error {
390+
return errors.New("random")
391+
},
392+
})
393+
pod := mockPod(kapi.PodSucceeded, 0)
394+
395+
err := ctrl.HandleBuildPodDeletion(pod)
396+
if err == nil {
397+
t.Error("Expected random error, but got none!")
398+
}
399+
}

0 commit comments

Comments
 (0)