Skip to content

Commit a0d7ce2

Browse files
Merge pull request #14596 from csrwng/buildconfig_controller
Refactor BuildConfig controller to use Informers
2 parents dc2fa5a + da9b120 commit a0d7ce2

File tree

11 files changed

+398
-382
lines changed

11 files changed

+398
-382
lines changed
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package cache
2+
3+
import (
4+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
5+
6+
buildapi "github.com/openshift/origin/pkg/build/api"
7+
cacheclient "github.com/openshift/origin/pkg/client/cache"
8+
)
9+
10+
// NewBuildConfigGetter returns an object that implements the buildclient BuildConfigGetter interface
11+
// using a StoreToBuildConfigLister
12+
func NewBuildConfigGetter(lister cacheclient.StoreToBuildConfigLister) *buildConfigGetter {
13+
return &buildConfigGetter{
14+
lister: lister,
15+
}
16+
}
17+
18+
type buildConfigGetter struct {
19+
lister cacheclient.StoreToBuildConfigLister
20+
}
21+
22+
// Get retrieves a buildconfig from the cache
23+
func (g *buildConfigGetter) Get(namespace, name string, options metav1.GetOptions) (*buildapi.BuildConfig, error) {
24+
return g.lister.BuildConfigs(namespace).Get(name, options)
25+
}

pkg/build/client/cache/builds.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package cache
2+
3+
import (
4+
"fmt"
5+
6+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
7+
"k8s.io/apimachinery/pkg/labels"
8+
9+
buildapi "github.com/openshift/origin/pkg/build/api"
10+
cacheclient "github.com/openshift/origin/pkg/client/cache"
11+
)
12+
13+
// NewBuildLister returns an object that implements the buildclient BuildLister interface
14+
// using a StoreToBuildLister
15+
func NewBuildLister(lister *cacheclient.StoreToBuildLister) *buildLister {
16+
return &buildLister{
17+
lister: lister,
18+
}
19+
}
20+
21+
type buildLister struct {
22+
lister *cacheclient.StoreToBuildLister
23+
}
24+
25+
// List returns a BuildList with the given namespace and get options. Only the LabelSelector
26+
// from the ListOptions is honored.
27+
func (l *buildLister) List(namespace string, opts metav1.ListOptions) (*buildapi.BuildList, error) {
28+
selector, err := labels.Parse(opts.LabelSelector)
29+
if err != nil {
30+
return nil, fmt.Errorf("invalid label selector %q: %v", opts.LabelSelector, err)
31+
}
32+
builds, err := l.lister.Builds(namespace).List(selector)
33+
if err != nil {
34+
return nil, err
35+
}
36+
return buildList(builds), nil
37+
}
38+
39+
func buildList(builds []*buildapi.Build) *buildapi.BuildList {
40+
items := []buildapi.Build{}
41+
for _, b := range builds {
42+
if b != nil {
43+
items = append(items, *b)
44+
}
45+
}
46+
return &buildapi.BuildList{
47+
Items: items,
48+
}
49+
}

pkg/build/controller/build/build_controller.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727
buildapi "github.com/openshift/origin/pkg/build/api"
2828
"github.com/openshift/origin/pkg/build/api/validation"
2929
buildclient "github.com/openshift/origin/pkg/build/client"
30+
buildcacheclient "github.com/openshift/origin/pkg/build/client/cache"
3031
"github.com/openshift/origin/pkg/build/controller/common"
3132
"github.com/openshift/origin/pkg/build/controller/policy"
3233
"github.com/openshift/origin/pkg/build/controller/strategy"
@@ -79,6 +80,7 @@ type BuildController struct {
7980
// create a new BuildController
8081
type BuildControllerParams struct {
8182
BuildInformer shared.BuildInformer
83+
BuildConfigInformer shared.BuildConfigInformer
8284
ImageStreamInformer imageinformers.ImageStreamInformer
8385
PodInformer kcoreinformers.PodInformer
8486
SecretInformer kcoreinformers.SecretInformer
@@ -98,10 +100,15 @@ func NewBuildController(params *BuildControllerParams) *BuildController {
98100
eventBroadcaster.StartRecordingToSink(&v1core.EventSinkImpl{Interface: v1core.New(params.KubeClientExternal.Core().RESTClient()).Events("")})
99101

100102
buildClient := buildclient.NewOSClientBuildClient(params.OpenshiftClient)
101-
buildConfigGetter := buildclient.NewOSClientBuildConfigClient(params.OpenshiftClient)
103+
// TODO: Switch to using the cache build lister when we figure out
104+
// what is wrong with retrieving by index
105+
// buildLister := buildcacheclient.NewBuildLister(params.BuildInformer.Lister())
106+
_ = buildcacheclient.NewBuildLister(params.BuildInformer.Lister())
107+
buildLister := buildClient
108+
buildConfigGetter := buildcacheclient.NewBuildConfigGetter(params.BuildConfigInformer.Lister())
102109
c := &BuildController{
103110
buildPatcher: buildClient,
104-
buildLister: buildClient,
111+
buildLister: buildLister,
105112
buildConfigGetter: buildConfigGetter,
106113
buildDeleter: buildClient,
107114
secretStore: params.SecretInformer.Lister(),

pkg/build/controller/build/build_controller_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -991,6 +991,7 @@ func newFakeBuildController(openshiftClient client.Interface, imageClient imagei
991991

992992
params := &BuildControllerParams{
993993
BuildInformer: informers.Builds(),
994+
BuildConfigInformer: informers.BuildConfigs(),
994995
PodInformer: informers.InternalKubernetesInformers().Core().InternalVersion().Pods(),
995996
ImageStreamInformer: imageInformers.Image().InternalVersion().ImageStreams(),
996997
SecretInformer: informers.InternalKubernetesInformers().Core().InternalVersion().Secrets(),
Lines changed: 273 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,273 @@
1+
package controller
2+
3+
import (
4+
"fmt"
5+
"time"
6+
7+
"github.com/golang/glog"
8+
kerrors "k8s.io/apimachinery/pkg/api/errors"
9+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
10+
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
11+
"k8s.io/apimachinery/pkg/util/wait"
12+
v1core "k8s.io/client-go/kubernetes/typed/core/v1"
13+
clientv1 "k8s.io/client-go/pkg/api/v1"
14+
"k8s.io/client-go/tools/cache"
15+
"k8s.io/client-go/tools/record"
16+
"k8s.io/client-go/util/workqueue"
17+
kapi "k8s.io/kubernetes/pkg/api"
18+
kexternalclientset "k8s.io/kubernetes/pkg/client/clientset_generated/clientset"
19+
kcontroller "k8s.io/kubernetes/pkg/controller"
20+
21+
buildapi "github.com/openshift/origin/pkg/build/api"
22+
buildclient "github.com/openshift/origin/pkg/build/client"
23+
cachebuildclient "github.com/openshift/origin/pkg/build/client/cache"
24+
buildutil "github.com/openshift/origin/pkg/build/controller/common"
25+
buildgenerator "github.com/openshift/origin/pkg/build/generator"
26+
osclient "github.com/openshift/origin/pkg/client"
27+
"github.com/openshift/origin/pkg/controller/shared"
28+
)
29+
30+
const (
31+
maxRetries = 15
32+
)
33+
34+
// configControllerFatalError represents a fatal error while generating a build.
35+
// An operation that fails because of a fatal error should not be retried.
36+
type configControllerFatalError struct {
37+
// Reason the fatal error occurred
38+
reason string
39+
}
40+
41+
// Error returns the error string for this fatal error
42+
func (e *configControllerFatalError) Error() string {
43+
return fmt.Sprintf("fatal: %s", e.reason)
44+
}
45+
46+
// IsFatal returns true if err is a fatal error
47+
func IsFatal(err error) bool {
48+
_, isFatal := err.(*configControllerFatalError)
49+
return isFatal
50+
}
51+
52+
type BuildConfigController struct {
53+
buildConfigInstantiator buildclient.BuildConfigInstantiator
54+
buildConfigGetter buildclient.BuildConfigGetter
55+
buildLister buildclient.BuildLister
56+
buildDeleter buildclient.BuildDeleter
57+
58+
buildConfigInformer cache.SharedIndexInformer
59+
60+
queue workqueue.RateLimitingInterface
61+
62+
buildConfigStoreSynced func() bool
63+
64+
recorder record.EventRecorder
65+
}
66+
67+
func NewBuildConfigController(openshiftClient osclient.Interface, kubeExternalClient kexternalclientset.Interface, buildConfigInformer shared.BuildConfigInformer, buildInformer shared.BuildInformer) *BuildConfigController {
68+
eventBroadcaster := record.NewBroadcaster()
69+
eventBroadcaster.StartRecordingToSink(&v1core.EventSinkImpl{Interface: v1core.New(kubeExternalClient.Core().RESTClient()).Events("")})
70+
71+
buildClient := buildclient.NewOSClientBuildClient(openshiftClient)
72+
buildConfigGetter := cachebuildclient.NewBuildConfigGetter(buildConfigInformer.Lister())
73+
buildConfigInstantiator := buildclient.NewOSClientBuildConfigInstantiatorClient(openshiftClient)
74+
// TODO: Switch to using the cache build lister when we figure out
75+
// what is wrong with retrieving by index
76+
// buildLister := cachebuildclient.NewBuildLister(buildInformer.Lister())
77+
_ = cachebuildclient.NewBuildLister(buildInformer.Lister())
78+
buildLister := buildClient
79+
80+
c := &BuildConfigController{
81+
buildConfigGetter: buildConfigGetter,
82+
buildLister: buildLister,
83+
buildDeleter: buildClient,
84+
buildConfigInstantiator: buildConfigInstantiator,
85+
86+
buildConfigInformer: buildConfigInformer.Informer(),
87+
88+
queue: workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter()),
89+
recorder: eventBroadcaster.NewRecorder(kapi.Scheme, clientv1.EventSource{Component: "buildconfig-controller"}),
90+
}
91+
92+
c.buildConfigInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
93+
UpdateFunc: c.buildConfigUpdated,
94+
AddFunc: c.buildConfigAdded,
95+
})
96+
97+
c.buildConfigStoreSynced = c.buildConfigInformer.HasSynced
98+
return c
99+
}
100+
101+
func (c *BuildConfigController) handleBuildConfig(bc *buildapi.BuildConfig) error {
102+
glog.V(4).Infof("Handling BuildConfig %s", bcDesc(bc))
103+
104+
if err := buildutil.HandleBuildPruning(bc.Name, bc.Namespace, c.buildLister, c.buildConfigGetter, c.buildDeleter); err != nil {
105+
utilruntime.HandleError(err)
106+
}
107+
108+
hasChangeTrigger := buildapi.HasTriggerType(buildapi.ConfigChangeBuildTriggerType, bc)
109+
110+
if !hasChangeTrigger {
111+
return nil
112+
}
113+
114+
if bc.Status.LastVersion > 0 {
115+
return nil
116+
}
117+
118+
glog.V(4).Infof("Running build for BuildConfig %s", bcDesc(bc))
119+
120+
buildTriggerCauses := []buildapi.BuildTriggerCause{}
121+
// instantiate new build
122+
lastVersion := int64(0)
123+
request := &buildapi.BuildRequest{
124+
TriggeredBy: append(buildTriggerCauses,
125+
buildapi.BuildTriggerCause{
126+
Message: buildapi.BuildTriggerCauseConfigMsg,
127+
}),
128+
ObjectMeta: metav1.ObjectMeta{
129+
Name: bc.Name,
130+
Namespace: bc.Namespace,
131+
},
132+
LastVersion: &lastVersion,
133+
}
134+
if _, err := c.buildConfigInstantiator.Instantiate(bc.Namespace, request); err != nil {
135+
var instantiateErr error
136+
if kerrors.IsConflict(err) {
137+
instantiateErr = fmt.Errorf("unable to instantiate Build for BuildConfig %s due to a conflicting update: %v", bcDesc(bc), err)
138+
utilruntime.HandleError(instantiateErr)
139+
} else if buildgenerator.IsFatal(err) || kerrors.IsNotFound(err) || kerrors.IsBadRequest(err) || kerrors.IsForbidden(err) {
140+
instantiateErr = fmt.Errorf("gave up on Build for BuildConfig %s due to fatal error: %v", bcDesc(bc), err)
141+
utilruntime.HandleError(instantiateErr)
142+
c.recorder.Event(bc, kapi.EventTypeWarning, "BuildConfigInstantiateFailed", instantiateErr.Error())
143+
return &configControllerFatalError{err.Error()}
144+
} else {
145+
instantiateErr = fmt.Errorf("error instantiating Build from BuildConfig %s: %v", bcDesc(bc), err)
146+
c.recorder.Event(bc, kapi.EventTypeWarning, "BuildConfigInstantiateFailed", instantiateErr.Error())
147+
utilruntime.HandleError(instantiateErr)
148+
}
149+
return instantiateErr
150+
}
151+
return nil
152+
}
153+
154+
// buildConfigAdded is called by the buildconfig informer event handler whenever a
155+
// buildconfig is created
156+
func (c *BuildConfigController) buildConfigAdded(obj interface{}) {
157+
bc := obj.(*buildapi.BuildConfig)
158+
c.enqueueBuildConfig(bc)
159+
}
160+
161+
// buildConfigUpdated gets called by the buildconfig informer event handler whenever a
162+
// buildconfig is updated or there is a relist of buildconfigs
163+
func (c *BuildConfigController) buildConfigUpdated(old, cur interface{}) {
164+
bc := cur.(*buildapi.BuildConfig)
165+
c.enqueueBuildConfig(bc)
166+
}
167+
168+
// enqueueBuild adds the given build to the queue.
169+
func (c *BuildConfigController) enqueueBuildConfig(bc *buildapi.BuildConfig) {
170+
key, err := kcontroller.KeyFunc(bc)
171+
if err != nil {
172+
utilruntime.HandleError(fmt.Errorf("couldn't get key for buildconfig %#v: %v", bc, err))
173+
return
174+
}
175+
c.queue.Add(key)
176+
}
177+
178+
// Run begins watching and syncing.
179+
func (c *BuildConfigController) Run(workers int, stopCh <-chan struct{}) {
180+
defer utilruntime.HandleCrash()
181+
defer c.queue.ShutDown()
182+
183+
// Wait for the controller stores to sync before starting any work in this controller.
184+
if !cache.WaitForCacheSync(stopCh, c.buildConfigStoreSynced) {
185+
utilruntime.HandleError(fmt.Errorf("timed out waiting for caches to sync"))
186+
return
187+
}
188+
189+
glog.Infof("Starting buildconfig controller")
190+
191+
for i := 0; i < workers; i++ {
192+
go wait.Until(c.worker, time.Second, stopCh)
193+
}
194+
195+
<-stopCh
196+
glog.Infof("Shutting down buildconfig controller")
197+
}
198+
199+
func (c *BuildConfigController) worker() {
200+
for {
201+
if quit := c.work(); quit {
202+
return
203+
}
204+
}
205+
}
206+
207+
// work gets the next build from the queue and invokes handleBuild on it
208+
func (c *BuildConfigController) work() bool {
209+
key, quit := c.queue.Get()
210+
if quit {
211+
return true
212+
}
213+
214+
defer c.queue.Done(key)
215+
216+
bc, err := c.getBuildConfigByKey(key.(string))
217+
if err != nil {
218+
c.handleError(err, key)
219+
return false
220+
}
221+
if bc == nil {
222+
return false
223+
}
224+
225+
err = c.handleBuildConfig(bc)
226+
c.handleError(err, key)
227+
228+
return false
229+
}
230+
231+
// handleError is called by the main work loop to check the return of calling handleBuildConfig
232+
// If an error occurred, then the key is re-added to the queue unless it has been retried too many
233+
// times.
234+
func (c *BuildConfigController) handleError(err error, key interface{}) {
235+
if err == nil {
236+
c.queue.Forget(key)
237+
return
238+
}
239+
240+
if IsFatal(err) {
241+
glog.V(2).Infof("Will not retry fatal error for key %v: %v", key, err)
242+
c.queue.Forget(key)
243+
return
244+
}
245+
246+
if c.queue.NumRequeues(key) < maxRetries {
247+
glog.V(4).Infof("Retrying key %v: %v", key, err)
248+
c.queue.AddRateLimited(key)
249+
return
250+
}
251+
252+
glog.V(2).Infof("Giving up retrying %v: %v", key, err)
253+
c.queue.Forget(key)
254+
}
255+
256+
// getBuildConfigByKey looks up a buildconfig by key in the buildConfigInformer cache
257+
func (c *BuildConfigController) getBuildConfigByKey(key string) (*buildapi.BuildConfig, error) {
258+
obj, exists, err := c.buildConfigInformer.GetIndexer().GetByKey(key)
259+
if err != nil {
260+
glog.V(2).Infof("Unable to retrieve buildconfig %s from store: %v", key, err)
261+
return nil, err
262+
}
263+
if !exists {
264+
glog.V(2).Infof("Buildconfig %q has been deleted", key)
265+
return nil, nil
266+
}
267+
268+
return obj.(*buildapi.BuildConfig), nil
269+
}
270+
271+
func bcDesc(bc *buildapi.BuildConfig) string {
272+
return fmt.Sprintf("%s/%s (%d)", bc.Namespace, bc.Name, bc.Status.LastVersion)
273+
}

0 commit comments

Comments
 (0)