// Create creates a new ImageChangeController which is used to trigger builds when a new // image is available func (factory *ImageChangeControllerFactory) Create() controller.RunnableController { queue := cache.NewResyncableFIFO(cache.MetaNamespaceKeyFunc) cache.NewReflector(&imageStreamLW{factory.Client}, &imageapi.ImageStream{}, queue, 2*time.Minute).RunUntil(factory.Stop) store := cache.NewStore(cache.MetaNamespaceKeyFunc) cache.NewReflector(&buildConfigLW{client: factory.Client}, &buildapi.BuildConfig{}, store, 2*time.Minute).RunUntil(factory.Stop) imageChangeController := &buildcontroller.ImageChangeController{ BuildConfigStore: store, BuildConfigInstantiator: factory.BuildConfigInstantiator, } return &controller.RetryController{ Queue: queue, RetryManager: controller.NewQueueRetryManager( queue, cache.MetaNamespaceKeyFunc, retryFunc("ImageStream update", func(err error) bool { _, isFatal := err.(buildcontroller.ImageChangeControllerFatalError) return isFatal }), flowcontrol.NewTokenBucketRateLimiter(1, 10), ), Handle: func(obj interface{}) error { imageRepo := obj.(*imageapi.ImageStream) return imageChangeController.HandleImageRepo(imageRepo) }, } }
// Create creates a new ConfigChangeController which is used to trigger builds on creation func (factory *BuildConfigControllerFactory) Create() controller.RunnableController { queue := cache.NewResyncableFIFO(cache.MetaNamespaceKeyFunc) cache.NewReflector(&buildConfigLW{client: factory.Client}, &buildapi.BuildConfig{}, queue, 2*time.Minute).RunUntil(factory.Stop) eventBroadcaster := record.NewBroadcaster() eventBroadcaster.StartRecordingToSink(factory.KubeClient.Events("")) bcController := &buildcontroller.BuildConfigController{ BuildConfigInstantiator: factory.BuildConfigInstantiator, Recorder: eventBroadcaster.NewRecorder(kapi.EventSource{Component: "build-config-controller"}), } return &controller.RetryController{ Queue: queue, RetryManager: controller.NewQueueRetryManager( queue, cache.MetaNamespaceKeyFunc, retryFunc("BuildConfig", buildcontroller.IsFatal), flowcontrol.NewTokenBucketRateLimiter(1, 10)), Handle: func(obj interface{}) error { bc := obj.(*buildapi.BuildConfig) return bcController.HandleBuildConfig(bc) }, } }
// Create creates a new ImageChangeController which is used to trigger builds when a new // image is available func (factory *ImageChangeControllerFactory) Create() controller.RunnableController { queue := cache.NewResyncableFIFO(cache.MetaNamespaceKeyFunc) cache.NewReflector(&imageStreamLW{factory.Client}, &imageapi.ImageStream{}, queue, 2*time.Minute).RunUntil(factory.Stop) imageChangeController := &buildcontroller.ImageChangeController{ BuildConfigIndex: factory.BuildConfigIndex, BuildConfigInstantiator: factory.BuildConfigInstantiator, } // Wait for the bc store to sync before starting any work in this controller. factory.waitForSyncedStores() return &controller.RetryController{ Queue: queue, RetryManager: controller.NewQueueRetryManager( queue, cache.MetaNamespaceKeyFunc, retryFunc("ImageStream update", nil), flowcontrol.NewTokenBucketRateLimiter(1, 10), ), Handle: func(obj interface{}) error { imageRepo := obj.(*imageapi.ImageStream) return imageChangeController.HandleImageStream(imageRepo) }, } }
// Create constructs a BuildPodController func (factory *BuildPodControllerFactory) Create() controller.RunnableController { factory.buildStore = cache.NewStore(cache.MetaNamespaceKeyFunc) cache.NewReflector(&buildLW{client: factory.OSClient}, &buildapi.Build{}, factory.buildStore, 2*time.Minute).RunUntil(factory.Stop) queue := cache.NewResyncableFIFO(cache.MetaNamespaceKeyFunc) cache.NewReflector(&podLW{client: factory.KubeClient}, &kapi.Pod{}, queue, 2*time.Minute).RunUntil(factory.Stop) client := ControllerClient{factory.KubeClient, factory.OSClient} buildPodController := &buildcontroller.BuildPodController{ BuildStore: factory.buildStore, BuildUpdater: factory.BuildUpdater, SecretClient: factory.KubeClient, PodManager: client, } return &controller.RetryController{ Queue: queue, RetryManager: controller.NewQueueRetryManager( queue, cache.MetaNamespaceKeyFunc, retryFunc("BuildPod", nil), flowcontrol.NewTokenBucketRateLimiter(1, 10)), Handle: func(obj interface{}) error { pod := obj.(*kapi.Pod) return buildPodController.HandlePod(pod) }, } }
// Create creates an ImageChangeController. func (factory *ImageChangeControllerFactory) Create() controller.RunnableController { imageStreamLW := &cache.ListWatch{ ListFunc: func(options kapi.ListOptions) (runtime.Object, error) { return factory.Client.ImageStreams(kapi.NamespaceAll).List(options) }, WatchFunc: func(options kapi.ListOptions) (watch.Interface, error) { return factory.Client.ImageStreams(kapi.NamespaceAll).Watch(options) }, } queue := cache.NewResyncableFIFO(cache.MetaNamespaceKeyFunc) cache.NewReflector(imageStreamLW, &imageapi.ImageStream{}, queue, 2*time.Minute).Run() deploymentConfigLW := &cache.ListWatch{ ListFunc: func(options kapi.ListOptions) (runtime.Object, error) { return factory.Client.DeploymentConfigs(kapi.NamespaceAll).List(options) }, WatchFunc: func(options kapi.ListOptions) (watch.Interface, error) { return factory.Client.DeploymentConfigs(kapi.NamespaceAll).Watch(options) }, } store := cache.NewStore(cache.MetaNamespaceKeyFunc) cache.NewReflector(deploymentConfigLW, &deployapi.DeploymentConfig{}, store, 2*time.Minute).Run() changeController := &ImageChangeController{ listDeploymentConfigs: func() ([]*deployapi.DeploymentConfig, error) { configs := []*deployapi.DeploymentConfig{} objs := store.List() for _, obj := range objs { configs = append(configs, obj.(*deployapi.DeploymentConfig)) } return configs, nil }, client: factory.Client, } return &controller.RetryController{ Queue: queue, RetryManager: controller.NewQueueRetryManager( queue, cache.MetaNamespaceKeyFunc, func(obj interface{}, err error, retries controller.Retry) bool { utilruntime.HandleError(err) if _, isFatal := err.(fatalError); isFatal { return false } if retries.Count > 2 { return false } return true }, flowcontrol.NewTokenBucketRateLimiter(1, 10), ), Handle: func(obj interface{}) error { repo := obj.(*imageapi.ImageStream) return changeController.Handle(repo) }, } }
// Create constructs a BuildController func (factory *BuildControllerFactory) Create() controller.RunnableController { queue := cache.NewResyncableFIFO(cache.MetaNamespaceKeyFunc) cache.NewReflector(&buildLW{client: factory.OSClient}, &buildapi.Build{}, queue, 2*time.Minute).RunUntil(factory.Stop) eventBroadcaster := record.NewBroadcaster() eventBroadcaster.StartRecordingToSink(&kcoreclient.EventSinkImpl{Interface: factory.KubeClient.Core().Events("")}) client := ControllerClient{factory.KubeClient, factory.OSClient} buildController := &buildcontroller.BuildController{ BuildUpdater: factory.BuildUpdater, BuildLister: factory.BuildLister, ImageStreamClient: client, PodManager: client, RunPolicies: policy.GetAllRunPolicies(factory.BuildLister, factory.BuildUpdater), BuildStrategy: &typeBasedFactoryStrategy{ DockerBuildStrategy: factory.DockerBuildStrategy, SourceBuildStrategy: factory.SourceBuildStrategy, CustomBuildStrategy: factory.CustomBuildStrategy, }, Recorder: eventBroadcaster.NewRecorder(kapi.EventSource{Component: "build-controller"}), BuildDefaults: factory.BuildDefaults, BuildOverrides: factory.BuildOverrides, } return &controller.RetryController{ Queue: queue, RetryManager: controller.NewQueueRetryManager( queue, cache.MetaNamespaceKeyFunc, limitedLogAndRetry(factory.BuildUpdater, 30*time.Minute), flowcontrol.NewTokenBucketRateLimiter(1, 10)), Handle: func(obj interface{}) error { build := obj.(*buildapi.Build) err := buildController.HandleBuild(build) if err != nil { // Update the build status message only if it changed. if msg := errors.ErrorToSentence(err); build.Status.Message != msg { // Set default Reason. if len(build.Status.Reason) == 0 { build.Status.Reason = buildapi.StatusReasonError } build.Status.Message = msg if err := buildController.BuildUpdater.Update(build.Namespace, build); err != nil { glog.V(2).Infof("Failed to update status message of Build %s/%s: %v", build.Namespace, build.Name, err) } buildController.Recorder.Eventf(build, kapi.EventTypeWarning, "HandleBuildError", "Build has error: %v", err) } } return err }, } }
// Create creates an ImportController. func (f *ImportControllerFactory) Create() (controller.RunnableController, controller.StoppableController) { lw := &cache.ListWatch{ ListFunc: func(options kapi.ListOptions) (runtime.Object, error) { return f.Client.ImageStreams(kapi.NamespaceAll).List(options) }, WatchFunc: func(options kapi.ListOptions) (watch.Interface, error) { return f.Client.ImageStreams(kapi.NamespaceAll).Watch(options) }, } q := cache.NewResyncableFIFO(cache.MetaNamespaceKeyFunc) cache.NewReflector(lw, &api.ImageStream{}, q, f.ResyncInterval).Run() // instantiate a scheduled importer using a number of buckets buckets := 4 switch { case f.MinimumCheckInterval > time.Hour: buckets = 8 case f.MinimumCheckInterval < 10*time.Minute: buckets = 2 } seconds := f.MinimumCheckInterval / time.Second bucketQPS := 1.0 / float32(seconds) * float32(buckets) limiter := flowcontrol.NewTokenBucketRateLimiter(bucketQPS, 1) b := newScheduled(f.ScheduleEnabled, f.Client, buckets, limiter, f.ImportRateLimiter) // instantiate an importer for changes that happen to the image stream changed := &controller.RetryController{ Queue: q, RetryManager: controller.NewQueueRetryManager( q, cache.MetaNamespaceKeyFunc, func(obj interface{}, err error, retries controller.Retry) bool { utilruntime.HandleError(err) return retries.Count < 5 }, flowcontrol.NewTokenBucketRateLimiter(1, 10), ), Handle: b.Handle, } return changed, b.scheduler }
// NewPodWatch creates a pod watching function which is backed by a // FIFO/reflector pair. This avoids managing watches directly. // A stop channel to close the watch's reflector is also returned. // It is the caller's responsibility to defer closing the stop channel to prevent leaking resources. func NewPodWatch(client kcoreclient.PodInterface, namespace, name, resourceVersion string, stopChannel chan struct{}) func() *kapi.Pod { fieldSelector := fields.OneTermEqualSelector("metadata.name", name) podLW := &cache.ListWatch{ ListFunc: func(options kapi.ListOptions) (runtime.Object, error) { options.FieldSelector = fieldSelector return client.List(options) }, WatchFunc: func(options kapi.ListOptions) (watch.Interface, error) { options.FieldSelector = fieldSelector return client.Watch(options) }, } queue := cache.NewResyncableFIFO(cache.MetaNamespaceKeyFunc) cache.NewReflector(podLW, &kapi.Pod{}, queue, 1*time.Minute).RunUntil(stopChannel) return func() *kapi.Pod { obj := cache.Pop(queue) return obj.(*kapi.Pod) } }
// This test ensures that when events are retried, the // requeue rate does not exceed the configured rate limit, // including burst behavior. func TestRetryController_ratelimit(t *testing.T) { keyFunc := func(obj interface{}) (string, error) { return "key", nil } fifo := kcache.NewResyncableFIFO(keyFunc) limiter := &mockLimiter{} retryManager := NewQueueRetryManager(fifo, keyFunc, func(_ interface{}, _ error, r Retry) bool { return r.Count < 15 }, limiter, ) for i := 0; i < 10; i++ { retryManager.Retry("key", nil) } if limiter.count != 10 { t.Fatalf("Retries did not invoke rate limiter, expected %d got %d", 10, limiter.count) } }
// Create creates a NamespaceController. func (factory *NamespaceControllerFactory) Create() controller.RunnableController { namespaceLW := &cache.ListWatch{ ListFunc: func(options kapi.ListOptions) (runtime.Object, error) { return factory.KubeClient.Core().Namespaces().List(options) }, WatchFunc: func(options kapi.ListOptions) (watch.Interface, error) { return factory.KubeClient.Core().Namespaces().Watch(options) }, } queue := cache.NewResyncableFIFO(cache.MetaNamespaceKeyFunc) cache.NewReflector(namespaceLW, &kapi.Namespace{}, queue, 1*time.Minute).Run() namespaceController := &NamespaceController{ Client: factory.Client, KubeClient: factory.KubeClient, } return &controller.RetryController{ Queue: queue, RetryManager: controller.NewQueueRetryManager( queue, cache.MetaNamespaceKeyFunc, func(obj interface{}, err error, retries controller.Retry) bool { utilruntime.HandleError(err) if _, isFatal := err.(fatalError); isFatal { return false } if retries.Count > 0 { return false } return true }, flowcontrol.NewTokenBucketRateLimiter(1, 10), ), Handle: func(obj interface{}) error { namespace := obj.(*kapi.Namespace) return namespaceController.Handle(namespace) }, } }
// This test ensures that when an asynchronous state update is received // on the queue during failed event handling, that the updated state is // retried, NOT the event that failed (which is now stale). func TestRetryController_realFifoEventOrdering(t *testing.T) { keyFunc := func(obj interface{}) (string, error) { return obj.(testObj).id, nil } fifo := kcache.NewResyncableFIFO(keyFunc) wg := sync.WaitGroup{} wg.Add(1) controller := &RetryController{ Queue: fifo, RetryManager: NewQueueRetryManager(fifo, keyFunc, func(_ interface{}, _ error, _ Retry) bool { return true }, flowcontrol.NewTokenBucketRateLimiter(1000, 10)), Handle: func(obj interface{}) error { if e, a := 1, obj.(testObj).value; e != a { t.Fatalf("expected to handle test value %d, got %d", e, a) } go func() { fifo.Add(testObj{"a", 2}) wg.Done() }() wg.Wait() return fmt.Errorf("retryable error") }, } fifo.Add(testObj{"a", 1}) controller.handleOne(kcache.Pop(fifo)) if e, a := 1, len(fifo.List()); e != a { t.Fatalf("expected queue length %d, got %d", e, a) } obj := kcache.Pop(fifo) if e, a := 2, obj.(testObj).value; e != a { t.Fatalf("expected queued value %d, got %d", e, a) } }
// Create creates a Allocation. func (f *AllocationFactory) Create() controller.RunnableController { if f.Queue == nil { lw := &cache.ListWatch{ ListFunc: func(options kapi.ListOptions) (runtime.Object, error) { return f.Client.List(options) }, WatchFunc: func(options kapi.ListOptions) (watch.Interface, error) { return f.Client.Watch(options) }, } q := cache.NewResyncableFIFO(cache.MetaNamespaceKeyFunc) cache.NewReflector(lw, &kapi.Namespace{}, q, 10*time.Minute).Run() f.Queue = q } c := &Allocation{ uid: f.UIDAllocator, mcs: f.MCSAllocator, client: f.Client, } return &controller.RetryController{ Queue: f.Queue, RetryManager: controller.NewQueueRetryManager( f.Queue, cache.MetaNamespaceKeyFunc, func(obj interface{}, err error, retries controller.Retry) bool { utilruntime.HandleError(err) return retries.Count < 5 }, flowcontrol.NewTokenBucketRateLimiter(1, 10), ), Handle: func(obj interface{}) error { r := obj.(*kapi.Namespace) return c.Next(r) }, } }