Skip to content

Commit 75250a7

Browse files
committed
Refactor BuildConfig controller to use Informers
1 parent 3697263 commit 75250a7

File tree

10 files changed

+388
-382
lines changed

10 files changed

+388
-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: 5 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,11 @@ 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+
buildLister := buildcacheclient.NewBuildLister(params.BuildInformer.Lister())
104+
buildConfigGetter := buildcacheclient.NewBuildConfigGetter(params.BuildConfigInformer.Lister())
102105
c := &BuildController{
103106
buildPatcher: buildClient,
104-
buildLister: buildClient,
107+
buildLister: buildLister,
105108
buildConfigGetter: buildConfigGetter,
106109
buildDeleter: buildClient,
107110
secretStore: params.SecretInformer.Lister(),
Lines changed: 268 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,268 @@
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+
buildLister := cachebuildclient.NewBuildLister(buildInformer.Lister())
75+
c := &BuildConfigController{
76+
buildConfigGetter: buildConfigGetter,
77+
buildLister: buildLister,
78+
buildDeleter: buildClient,
79+
buildConfigInstantiator: buildConfigInstantiator,
80+
81+
buildConfigInformer: buildConfigInformer.Informer(),
82+
83+
queue: workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter()),
84+
recorder: eventBroadcaster.NewRecorder(kapi.Scheme, clientv1.EventSource{Component: "buildconfig-controller"}),
85+
}
86+
87+
c.buildConfigInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
88+
UpdateFunc: c.buildConfigUpdated,
89+
AddFunc: c.buildConfigAdded,
90+
})
91+
92+
c.buildConfigStoreSynced = c.buildConfigInformer.HasSynced
93+
return c
94+
}
95+
96+
func (c *BuildConfigController) handleBuildConfig(bc *buildapi.BuildConfig) error {
97+
glog.V(4).Infof("Handling BuildConfig %s", bcDesc(bc))
98+
99+
if err := buildutil.HandleBuildPruning(bc.Name, bc.Namespace, c.buildLister, c.buildConfigGetter, c.buildDeleter); err != nil {
100+
utilruntime.HandleError(err)
101+
}
102+
103+
hasChangeTrigger := buildapi.HasTriggerType(buildapi.ConfigChangeBuildTriggerType, bc)
104+
105+
if !hasChangeTrigger {
106+
return nil
107+
}
108+
109+
if bc.Status.LastVersion > 0 {
110+
return nil
111+
}
112+
113+
glog.V(4).Infof("Running build for BuildConfig %s", bcDesc(bc))
114+
115+
buildTriggerCauses := []buildapi.BuildTriggerCause{}
116+
// instantiate new build
117+
lastVersion := int64(0)
118+
request := &buildapi.BuildRequest{
119+
TriggeredBy: append(buildTriggerCauses,
120+
buildapi.BuildTriggerCause{
121+
Message: buildapi.BuildTriggerCauseConfigMsg,
122+
}),
123+
ObjectMeta: metav1.ObjectMeta{
124+
Name: bc.Name,
125+
Namespace: bc.Namespace,
126+
},
127+
LastVersion: &lastVersion,
128+
}
129+
if _, err := c.buildConfigInstantiator.Instantiate(bc.Namespace, request); err != nil {
130+
var instantiateErr error
131+
if kerrors.IsConflict(err) {
132+
instantiateErr = fmt.Errorf("unable to instantiate Build for BuildConfig %s due to a conflicting update: %v", bcDesc(bc), err)
133+
utilruntime.HandleError(instantiateErr)
134+
} else if buildgenerator.IsFatal(err) || kerrors.IsNotFound(err) || kerrors.IsBadRequest(err) || kerrors.IsForbidden(err) {
135+
instantiateErr = fmt.Errorf("gave up on Build for BuildConfig %s due to fatal error: %v", bcDesc(bc), err)
136+
utilruntime.HandleError(instantiateErr)
137+
c.recorder.Event(bc, kapi.EventTypeWarning, "BuildConfigInstantiateFailed", instantiateErr.Error())
138+
return &configControllerFatalError{err.Error()}
139+
} else {
140+
instantiateErr = fmt.Errorf("error instantiating Build from BuildConfig %s: %v", bcDesc(bc), err)
141+
c.recorder.Event(bc, kapi.EventTypeWarning, "BuildConfigInstantiateFailed", instantiateErr.Error())
142+
utilruntime.HandleError(instantiateErr)
143+
}
144+
return instantiateErr
145+
}
146+
return nil
147+
}
148+
149+
// buildConfigAdded is called by the buildconfig informer event handler whenever a
150+
// buildconfig is created
151+
func (c *BuildConfigController) buildConfigAdded(obj interface{}) {
152+
bc := obj.(*buildapi.BuildConfig)
153+
c.enqueueBuildConfig(bc)
154+
}
155+
156+
// buildConfigUpdated gets called by the buildconfig informer event handler whenever a
157+
// buildconfig is updated or there is a relist of buildconfigs
158+
func (c *BuildConfigController) buildConfigUpdated(old, cur interface{}) {
159+
bc := cur.(*buildapi.BuildConfig)
160+
c.enqueueBuildConfig(bc)
161+
}
162+
163+
// enqueueBuild adds the given build to the queue.
164+
func (c *BuildConfigController) enqueueBuildConfig(bc *buildapi.BuildConfig) {
165+
key, err := kcontroller.KeyFunc(bc)
166+
if err != nil {
167+
utilruntime.HandleError(fmt.Errorf("couldn't get key for buildconfig %#v: %v", bc, err))
168+
return
169+
}
170+
c.queue.Add(key)
171+
}
172+
173+
// Run begins watching and syncing.
174+
func (c *BuildConfigController) Run(workers int, stopCh <-chan struct{}) {
175+
defer utilruntime.HandleCrash()
176+
defer c.queue.ShutDown()
177+
178+
// Wait for the controller stores to sync before starting any work in this controller.
179+
if !cache.WaitForCacheSync(stopCh, c.buildConfigStoreSynced) {
180+
utilruntime.HandleError(fmt.Errorf("timed out waiting for caches to sync"))
181+
return
182+
}
183+
184+
glog.Infof("Starting buildconfig controller")
185+
186+
for i := 0; i < workers; i++ {
187+
go wait.Until(c.worker, time.Second, stopCh)
188+
}
189+
190+
<-stopCh
191+
glog.Infof("Shutting down buildconfig controller")
192+
}
193+
194+
func (c *BuildConfigController) worker() {
195+
for {
196+
if quit := c.work(); quit {
197+
return
198+
}
199+
}
200+
}
201+
202+
// work gets the next build from the queue and invokes handleBuild on it
203+
func (c *BuildConfigController) work() bool {
204+
key, quit := c.queue.Get()
205+
if quit {
206+
return true
207+
}
208+
209+
defer c.queue.Done(key)
210+
211+
bc, err := c.getBuildConfigByKey(key.(string))
212+
if err != nil {
213+
c.handleError(err, key)
214+
return false
215+
}
216+
if bc == nil {
217+
return false
218+
}
219+
220+
err = c.handleBuildConfig(bc)
221+
c.handleError(err, key)
222+
223+
return false
224+
}
225+
226+
// handleError is called by the main work loop to check the return of calling handleBuildConfig
227+
// If an error occurred, then the key is re-added to the queue unless it has been retried too many
228+
// times.
229+
func (c *BuildConfigController) handleError(err error, key interface{}) {
230+
if err == nil {
231+
c.queue.Forget(key)
232+
return
233+
}
234+
235+
if IsFatal(err) {
236+
glog.V(2).Infof("Will not retry fatal error for key %v: %v", key, err)
237+
c.queue.Forget(key)
238+
return
239+
}
240+
241+
if c.queue.NumRequeues(key) < maxRetries {
242+
glog.V(4).Infof("Retrying key %v: %v", key, err)
243+
c.queue.AddRateLimited(key)
244+
return
245+
}
246+
247+
glog.V(2).Infof("Giving up retrying %v: %v", key, err)
248+
c.queue.Forget(key)
249+
}
250+
251+
// getBuildConfigByKey looks up a buildconfig by key in the buildConfigInformer cache
252+
func (c *BuildConfigController) getBuildConfigByKey(key string) (*buildapi.BuildConfig, error) {
253+
obj, exists, err := c.buildConfigInformer.GetIndexer().GetByKey(key)
254+
if err != nil {
255+
glog.V(2).Infof("Unable to retrieve buildconfig %s from store: %v", key, err)
256+
return nil, err
257+
}
258+
if !exists {
259+
glog.V(2).Infof("Buildconfig %q has been deleted", key)
260+
return nil, nil
261+
}
262+
263+
return obj.(*buildapi.BuildConfig), nil
264+
}
265+
266+
func bcDesc(bc *buildapi.BuildConfig) string {
267+
return fmt.Sprintf("%s/%s (%d)", bc.Namespace, bc.Name, bc.Status.LastVersion)
268+
}

0 commit comments

Comments
 (0)