You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@submarine.apache.org by pi...@apache.org on 2021/10/29 10:39:26 UTC

[submarine] branch master updated: SUBMARINE-1055. Use builder pattern to simplify construction of controller

This is an automated email from the ASF dual-hosted git repository.

pingsutw pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/submarine.git


The following commit(s) were added to refs/heads/master by this push:
     new e588327  SUBMARINE-1055. Use builder pattern to simplify construction of controller
e588327 is described below

commit e588327342b860c06de40751357f41bbaf8e4e84
Author: woodcutter-eric <yi...@gmail.com>
AuthorDate: Thu Oct 21 23:01:22 2021 +0800

    SUBMARINE-1055. Use builder pattern to simplify construction of controller
    
    ### What is this PR for?
    <!-- A few sentences describing the overall goals of the pull request's commits.
    First time? Check out the contributing guide - https://submarine.apache.org/contribution/contributions.html
    -->
    
    Now, the construction of controller is bulky. It needs a lot of arguments when making a new instance. We may use builder design pattern to solve this problem to make code more cleaner.
    
    ### What type of PR is it?
    [Improvement | Refactoring]
    
    ### Todos
    * [x] - Use builder pattern to reduce the number of parameters of NewController
    * [x] - Replace NewController with a higher level wrapper function
    * [x] - Partition initialization logic in the creation of a controller
    * [x] - Decouple event register and setup
    * [x] - Add interfaces to restrict access to other internal functions
    
    ### What is the Jira issue?
    <!-- * Open an issue on Jira https://issues.apache.org/jira/browse/SUBMARINE/
    * Put link here, and add [SUBMARINE-*Jira number*] in PR title, eg. `SUBMARINE-23. PR title`
    -->
    
    https://issues.apache.org/jira/projects/SUBMARINE/issues/SUBMARINE-1055
    
    ### How should this be tested?
    <!--
    * First time? Setup Travis CI as described on https://submarine.apache.org/contribution/contributions.html#continuous-integration
    * Strongly recommended: add automated unit tests for any new or changed behavior
    * Outline any manual steps to test the PR here.
    -->
    ### Screenshots (if appropriate)
    
    ### Questions:
    * Do the license files need updating? No
    * Are there breaking changes for older versions? No
    * Does this need new documentation? No
    
    Author: woodcutter-eric <yi...@gmail.com>
    
    Signed-off-by: Kevin <pi...@apache.org>
    
    Closes #780 from woodcutter-eric/SUBMARINE-1055 and squashes the following commits:
    
    9721794f [woodcutter-eric] SUBMARINE-1055. Add several builder event handler functions to internal
    97caf778 [woodcutter-eric] SUBMARINE-1055. Add several builder functions to internal
    3615ec98 [woodcutter-eric] SUBMARINE-1055. Fix controller builder design and typos
    0093b37d [woodcutter-eric] SUBMARINE-1055. Add interfaces for controller and builder
    15a75085 [woodcutter-eric] SUBMARINE-1055. Add license header for new files
    53857b39 [woodcutter-eric] SUBMARINE-1055. Replace the original controller creatiion process
    6467430b [woodcutter-eric] SUBMARINE-1055. Remove NewController() function
    a0e4db40 [woodcutter-eric] SUBMARINE-1055. Refactor controller event handlers
    453c5ba6 [woodcutter-eric] SUBMARINE-1055. Add controller builder
---
 submarine-cloud-v2/main.go                         |  50 ++++--
 submarine-cloud-v2/pkg/controller/controller.go    | 181 -------------------
 .../pkg/controller/controller_builder.go           | 115 +++++++++++++
 .../pkg/controller/controller_builder_config.go    | 149 ++++++++++++++++
 .../pkg/controller/controller_event_handlers.go    | 191 +++++++++++++++++++++
 5 files changed, 494 insertions(+), 192 deletions(-)

diff --git a/submarine-cloud-v2/main.go b/submarine-cloud-v2/main.go
index bfdba48..e7d3a00 100644
--- a/submarine-cloud-v2/main.go
+++ b/submarine-cloud-v2/main.go
@@ -86,17 +86,15 @@ func main() {
 	//       ex: namespace informer
 
 	// Create a Submarine operator
-	submarineController := controller.NewController(incluster, kubeClient, submarineClient, traefikClient,
-		kubeInformerFactory.Core().V1().Namespaces(),
-		kubeInformerFactory.Apps().V1().Deployments(),
-		kubeInformerFactory.Core().V1().Services(),
-		kubeInformerFactory.Core().V1().ServiceAccounts(),
-		kubeInformerFactory.Core().V1().PersistentVolumeClaims(),
-		kubeInformerFactory.Extensions().V1beta1().Ingresses(),
-		traefikInformerFactory.Traefik().V1alpha1().IngressRoutes(),
-		kubeInformerFactory.Rbac().V1().Roles(),
-		kubeInformerFactory.Rbac().V1().RoleBindings(),
-		submarineInformerFactory.Submarine().V1alpha1().Submarines())
+	submarineController := NewSubmarineController(
+		incluster,
+		kubeClient,
+		submarineClient,
+		traefikClient,
+		kubeInformerFactory,
+		submarineInformerFactory,
+		traefikInformerFactory,
+	)
 
 	// notice that there is no need to run Start methods in a separate goroutine. (i.e. go kubeInformerFactory.Start(stopCh)
 	// Start method is non-blocking and runs all registered informers in a dedicated goroutine.
@@ -110,6 +108,36 @@ func main() {
 	}
 }
 
+func NewSubmarineController(
+	incluster bool,
+	kubeClient *kubernetes.Clientset,
+	submarineClient *clientset.Clientset,
+	traefikClient *traefikclientset.Clientset,
+	kubeInformerFactory kubeinformers.SharedInformerFactory,
+	submarineInformerFactory informers.SharedInformerFactory,
+	traefikInformerFactory traefikinformers.SharedInformerFactory,
+) *controller.Controller {
+	bc := controller.NewControllerBuilderConfig()
+	bc.
+		InCluster(incluster).
+		WithKubeClientset(kubeClient).
+		WithSubmarineClientset(submarineClient).
+		WithTraefikClientset(traefikClient).
+		WithSubmarineInformer(submarineInformerFactory.Submarine().V1alpha1().Submarines()).
+		WithDeploymentInformer(kubeInformerFactory.Apps().V1().Deployments()).
+		WithNamespaceInformer(kubeInformerFactory.Core().V1().Namespaces()).
+		WithServiceInformer(kubeInformerFactory.Core().V1().Services()).
+		WithServiceAccountInformer(kubeInformerFactory.Core().V1().ServiceAccounts()).
+		WithPersistentVolumeClaimInformer(kubeInformerFactory.Core().V1().PersistentVolumeClaims()).
+		WithIngressInformer(kubeInformerFactory.Extensions().V1beta1().Ingresses()).
+		WithIngressRouteInformer(traefikInformerFactory.Traefik().V1alpha1().IngressRoutes()).
+		WithRoleInformer(kubeInformerFactory.Rbac().V1().Roles()).
+		WithRoleBindingInformer(kubeInformerFactory.Rbac().V1().RoleBindings())
+
+	return controller.NewControllerBuilder(bc).Build()
+
+}
+
 func init() {
 	flag.BoolVar(&incluster, "incluster", false, "Run submarine-operator in-cluster")
 	flag.StringVar(&kubeconfig, "kubeconfig", os.Getenv("HOME")+"/.kube/config", "Path to a kubeconfig. Only required if out-of-cluster.")
diff --git a/submarine-cloud-v2/pkg/controller/controller.go b/submarine-cloud-v2/pkg/controller/controller.go
index a55d9ba..ac8364b 100644
--- a/submarine-cloud-v2/pkg/controller/controller.go
+++ b/submarine-cloud-v2/pkg/controller/controller.go
@@ -25,26 +25,16 @@ import (
 
 	v1alpha1 "github.com/apache/submarine/submarine-cloud-v2/pkg/apis/submarine/v1alpha1"
 	clientset "github.com/apache/submarine/submarine-cloud-v2/pkg/client/clientset/versioned"
-	submarinescheme "github.com/apache/submarine/submarine-cloud-v2/pkg/client/clientset/versioned/scheme"
-	informers "github.com/apache/submarine/submarine-cloud-v2/pkg/client/informers/externalversions/submarine/v1alpha1"
 	listers "github.com/apache/submarine/submarine-cloud-v2/pkg/client/listers/submarine/v1alpha1"
 
 	appsv1 "k8s.io/api/apps/v1"
 	corev1 "k8s.io/api/core/v1"
-	extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
-	rbacv1 "k8s.io/api/rbac/v1"
 	"k8s.io/apimachinery/pkg/api/equality"
 	"k8s.io/apimachinery/pkg/api/errors"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
 	"k8s.io/apimachinery/pkg/util/wait"
-	appsinformers "k8s.io/client-go/informers/apps/v1"
-	coreinformers "k8s.io/client-go/informers/core/v1"
-	extinformers "k8s.io/client-go/informers/extensions/v1beta1"
-	rbacinformers "k8s.io/client-go/informers/rbac/v1"
 	"k8s.io/client-go/kubernetes"
-	"k8s.io/client-go/kubernetes/scheme"
-	typedcorev1 "k8s.io/client-go/kubernetes/typed/core/v1"
 	appslisters "k8s.io/client-go/listers/apps/v1"
 	corelisters "k8s.io/client-go/listers/core/v1"
 	extlisters "k8s.io/client-go/listers/extensions/v1beta1"
@@ -55,9 +45,7 @@ import (
 	"k8s.io/klog/v2"
 
 	traefik "github.com/traefik/traefik/v2/pkg/provider/kubernetes/crd/generated/clientset/versioned"
-	traefikinformers "github.com/traefik/traefik/v2/pkg/provider/kubernetes/crd/generated/informers/externalversions/traefik/v1alpha1"
 	traefiklisters "github.com/traefik/traefik/v2/pkg/provider/kubernetes/crd/generated/listers/traefik/v1alpha1"
-	traefikv1alpha1 "github.com/traefik/traefik/v2/pkg/provider/kubernetes/crd/traefik/v1alpha1"
 )
 
 const controllerAgentName = "submarine-controller"
@@ -134,175 +122,6 @@ type Controller struct {
 	incluster bool
 }
 
-// NewController returns a new sample controller
-func NewController(
-	incluster bool,
-	kubeclientset kubernetes.Interface,
-	submarineclientset clientset.Interface,
-	traefikclientset traefik.Interface,
-	namespaceInformer coreinformers.NamespaceInformer,
-	deploymentInformer appsinformers.DeploymentInformer,
-	serviceInformer coreinformers.ServiceInformer,
-	serviceaccountInformer coreinformers.ServiceAccountInformer,
-	persistentvolumeclaimInformer coreinformers.PersistentVolumeClaimInformer,
-	ingressInformer extinformers.IngressInformer,
-	ingressrouteInformer traefikinformers.IngressRouteInformer,
-	roleInformer rbacinformers.RoleInformer,
-	rolebindingInformer rbacinformers.RoleBindingInformer,
-	submarineInformer informers.SubmarineInformer) *Controller {
-
-	// Add Submarine types to the default Kubernetes Scheme so Events can be
-	// logged for Submarine types.
-	utilruntime.Must(submarinescheme.AddToScheme(scheme.Scheme))
-	klog.V(4).Info("Creating event broadcaster")
-	eventBroadcaster := record.NewBroadcaster()
-	eventBroadcaster.StartStructuredLogging(0)
-	eventBroadcaster.StartRecordingToSink(&typedcorev1.EventSinkImpl{Interface: kubeclientset.CoreV1().Events("")})
-	recorder := eventBroadcaster.NewRecorder(scheme.Scheme, corev1.EventSource{Component: controllerAgentName})
-
-	// Initialize controller
-	controller := &Controller{
-		kubeclientset:               kubeclientset,
-		submarineclientset:          submarineclientset,
-		traefikclientset:            traefikclientset,
-		submarinesLister:            submarineInformer.Lister(),
-		submarinesSynced:            submarineInformer.Informer().HasSynced,
-		namespaceLister:             namespaceInformer.Lister(),
-		deploymentLister:            deploymentInformer.Lister(),
-		serviceLister:               serviceInformer.Lister(),
-		serviceaccountLister:        serviceaccountInformer.Lister(),
-		persistentvolumeclaimLister: persistentvolumeclaimInformer.Lister(),
-		ingressLister:               ingressInformer.Lister(),
-		ingressrouteLister:          ingressrouteInformer.Lister(),
-		roleLister:                  roleInformer.Lister(),
-		rolebindingLister:           rolebindingInformer.Lister(),
-		workqueue:                   workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "Submarines"),
-		recorder:                    recorder,
-		incluster:                   incluster,
-	}
-
-	// Setting up event handler for Submarine
-	klog.Info("Setting up event handlers")
-	submarineInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
-		AddFunc: controller.enqueueSubmarine,
-		UpdateFunc: func(old, new interface{}) {
-			controller.enqueueSubmarine(new)
-		},
-	})
-
-	// Setting up event handler for other resources
-	namespaceInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
-		AddFunc: controller.handleObject,
-		UpdateFunc: func(old, new interface{}) {
-			newNamespace := new.(*corev1.Namespace)
-			oldNamespace := old.(*corev1.Namespace)
-			if newNamespace.ResourceVersion == oldNamespace.ResourceVersion {
-				return
-			}
-			controller.handleObject(new)
-		},
-		DeleteFunc: controller.handleObject,
-	})
-	deploymentInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
-		AddFunc: controller.handleObject,
-		UpdateFunc: func(old, new interface{}) {
-			newDeployment := new.(*appsv1.Deployment)
-			oldDeployment := old.(*appsv1.Deployment)
-			if newDeployment.ResourceVersion == oldDeployment.ResourceVersion {
-				return
-			}
-			controller.handleObject(new)
-		},
-		DeleteFunc: controller.handleObject,
-	})
-	serviceInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
-		AddFunc: controller.handleObject,
-		UpdateFunc: func(old, new interface{}) {
-			newService := new.(*corev1.Service)
-			oldService := old.(*corev1.Service)
-			if newService.ResourceVersion == oldService.ResourceVersion {
-				return
-			}
-			controller.handleObject(new)
-		},
-		DeleteFunc: controller.handleObject,
-	})
-	serviceaccountInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
-		AddFunc: controller.handleObject,
-		UpdateFunc: func(old, new interface{}) {
-			newServiceAccount := new.(*corev1.ServiceAccount)
-			oldServiceAccount := old.(*corev1.ServiceAccount)
-			if newServiceAccount.ResourceVersion == oldServiceAccount.ResourceVersion {
-				return
-			}
-			controller.handleObject(new)
-		},
-		DeleteFunc: controller.handleObject,
-	})
-	persistentvolumeclaimInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
-		AddFunc: controller.handleObject,
-		UpdateFunc: func(old, new interface{}) {
-			newPVC := new.(*corev1.PersistentVolumeClaim)
-			oldPVC := old.(*corev1.PersistentVolumeClaim)
-			if newPVC.ResourceVersion == oldPVC.ResourceVersion {
-				return
-			}
-			controller.handleObject(new)
-		},
-		DeleteFunc: controller.handleObject,
-	})
-	ingressInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
-		AddFunc: controller.handleObject,
-		UpdateFunc: func(old, new interface{}) {
-			newIngress := new.(*extensionsv1beta1.Ingress)
-			oldIngress := old.(*extensionsv1beta1.Ingress)
-			if newIngress.ResourceVersion == oldIngress.ResourceVersion {
-				return
-			}
-			controller.handleObject(new)
-		},
-		DeleteFunc: controller.handleObject,
-	})
-	ingressrouteInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
-		AddFunc: controller.handleObject,
-		UpdateFunc: func(old, new interface{}) {
-			newIngressRoute := new.(*traefikv1alpha1.IngressRoute)
-			oldIngressRoute := old.(*traefikv1alpha1.IngressRoute)
-			if newIngressRoute.ResourceVersion == oldIngressRoute.ResourceVersion {
-				return
-			}
-			controller.handleObject(new)
-		},
-		DeleteFunc: controller.handleObject,
-	})
-	roleInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
-		AddFunc: controller.handleObject,
-		UpdateFunc: func(old, new interface{}) {
-			newRole := new.(*rbacv1.Role)
-			oldRole := old.(*rbacv1.Role)
-			if newRole.ResourceVersion == oldRole.ResourceVersion {
-				return
-			}
-			controller.handleObject(new)
-		},
-		DeleteFunc: controller.handleObject,
-	})
-	rolebindingInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
-		AddFunc: controller.handleObject,
-		UpdateFunc: func(old, new interface{}) {
-			newRoleBinding := new.(*rbacv1.RoleBinding)
-			oldRoleBinding := old.(*rbacv1.RoleBinding)
-			if newRoleBinding.ResourceVersion == oldRoleBinding.ResourceVersion {
-				return
-			}
-			controller.handleObject(new)
-		},
-		DeleteFunc: controller.handleObject,
-	})
-
-	return controller
-}
-
 func (c *Controller) Run(threadiness int, stopCh <-chan struct{}) error {
 	defer utilruntime.HandleCrash()
 	defer c.workqueue.ShutDown()
diff --git a/submarine-cloud-v2/pkg/controller/controller_builder.go b/submarine-cloud-v2/pkg/controller/controller_builder.go
new file mode 100644
index 0000000..808f543
--- /dev/null
+++ b/submarine-cloud-v2/pkg/controller/controller_builder.go
@@ -0,0 +1,115 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package controller
+
+import (
+	submarinescheme "github.com/apache/submarine/submarine-cloud-v2/pkg/client/clientset/versioned/scheme"
+	corev1 "k8s.io/api/core/v1"
+	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
+	"k8s.io/client-go/kubernetes/scheme"
+	typedcorev1 "k8s.io/client-go/kubernetes/typed/core/v1"
+	"k8s.io/client-go/tools/record"
+	"k8s.io/client-go/util/workqueue"
+	"k8s.io/klog/v2"
+)
+
+type ControllerBuilder struct {
+	controller *Controller
+	config     *BuilderConfig
+}
+
+func NewControllerBuilder(config *BuilderConfig) *ControllerBuilder {
+	return &ControllerBuilder{
+		controller: &Controller{},
+		config:     config,
+	}
+}
+
+func (cb *ControllerBuilder) Build() *Controller {
+	cb.initialize()
+	cb.addClientsets()
+	cb.addListers()
+	cb.addEventHandlers()
+
+	return cb.controller
+}
+
+func (cb *ControllerBuilder) initialize() *ControllerBuilder {
+	// Add Submarine types to the default Kubernetes Scheme so Events can be
+	// logged for Submarine types.
+	utilruntime.Must(submarinescheme.AddToScheme(scheme.Scheme))
+	klog.V(4).Info("Creating event broadcaster")
+
+	eventBroadcaster := record.NewBroadcaster()
+	eventBroadcaster.StartStructuredLogging(0)
+	eventBroadcaster.StartRecordingToSink(&typedcorev1.EventSinkImpl{Interface: cb.config.kubeclientset.CoreV1().Events("")})
+	recorder := eventBroadcaster.NewRecorder(scheme.Scheme, corev1.EventSource{Component: controllerAgentName})
+
+	workqueue := workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "Submarines")
+
+	cb.controller.incluster = cb.config.incluster
+	cb.controller.recorder = recorder
+	cb.controller.workqueue = workqueue
+
+	return cb
+}
+
+func (cb *ControllerBuilder) addClientsets() *ControllerBuilder {
+	cb.controller.kubeclientset = cb.config.kubeclientset
+	cb.controller.submarineclientset = cb.config.submarineclientset
+	cb.controller.traefikclientset = cb.config.traefikclientset
+
+	return cb
+}
+
+func (cb *ControllerBuilder) addListers() *ControllerBuilder {
+	cb.controller.submarinesLister = cb.config.submarineInformer.Lister()
+	cb.controller.submarinesSynced = cb.config.submarineInformer.Informer().HasSynced
+
+	cb.controller.deploymentLister = cb.config.deploymentInformer.Lister()
+	cb.controller.namespaceLister = cb.config.namespaceInformer.Lister()
+	cb.controller.serviceLister = cb.config.serviceInformer.Lister()
+	cb.controller.serviceaccountLister = cb.config.serviceaccountInformer.Lister()
+	cb.controller.persistentvolumeclaimLister = cb.config.persistentvolumeclaimInformer.Lister()
+	cb.controller.ingressLister = cb.config.ingressInformer.Lister()
+	cb.controller.ingressrouteLister = cb.config.ingressrouteInformer.Lister()
+	cb.controller.roleLister = cb.config.roleInformer.Lister()
+	cb.controller.rolebindingLister = cb.config.rolebindingInformer.Lister()
+
+	return cb
+}
+
+func (cb *ControllerBuilder) addEventHandlers() *ControllerBuilder {
+	klog.Info("Setting up event handlers")
+
+	// Setting up event handler for Submarine
+	cb.addSubmarineEventHandlers()
+
+	// Setting up event handler for other resources
+	cb.addNamespaceEventHandlers()
+	cb.addDeploymentEventHandlers()
+	cb.addServiceEventHandlers()
+	cb.addServiceAccountEventHandlers()
+	cb.addPersistentVolumeClaimEventHandlers()
+	cb.addIngressEventHandlers()
+	cb.addIngressRouteEventHandlers()
+	cb.addRoleEventHandlers()
+	cb.addRoleBindingEventHandlers()
+
+	return cb
+}
diff --git a/submarine-cloud-v2/pkg/controller/controller_builder_config.go b/submarine-cloud-v2/pkg/controller/controller_builder_config.go
new file mode 100644
index 0000000..1c48d27
--- /dev/null
+++ b/submarine-cloud-v2/pkg/controller/controller_builder_config.go
@@ -0,0 +1,149 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package controller
+
+import (
+	clientset "github.com/apache/submarine/submarine-cloud-v2/pkg/client/clientset/versioned"
+	informers "github.com/apache/submarine/submarine-cloud-v2/pkg/client/informers/externalversions/submarine/v1alpha1"
+	traefik "github.com/traefik/traefik/v2/pkg/provider/kubernetes/crd/generated/clientset/versioned"
+	traefikinformers "github.com/traefik/traefik/v2/pkg/provider/kubernetes/crd/generated/informers/externalversions/traefik/v1alpha1"
+	appsinformers "k8s.io/client-go/informers/apps/v1"
+	coreinformers "k8s.io/client-go/informers/core/v1"
+	extinformers "k8s.io/client-go/informers/extensions/v1beta1"
+	rbacinformers "k8s.io/client-go/informers/rbac/v1"
+	"k8s.io/client-go/kubernetes"
+)
+
+type BuilderConfig struct {
+	incluster                     bool
+	kubeclientset                 kubernetes.Interface
+	submarineclientset            clientset.Interface
+	traefikclientset              traefik.Interface
+	namespaceInformer             coreinformers.NamespaceInformer
+	deploymentInformer            appsinformers.DeploymentInformer
+	serviceInformer               coreinformers.ServiceInformer
+	serviceaccountInformer        coreinformers.ServiceAccountInformer
+	persistentvolumeclaimInformer coreinformers.PersistentVolumeClaimInformer
+	ingressInformer               extinformers.IngressInformer
+	ingressrouteInformer          traefikinformers.IngressRouteInformer
+	roleInformer                  rbacinformers.RoleInformer
+	rolebindingInformer           rbacinformers.RoleBindingInformer
+	submarineInformer             informers.SubmarineInformer
+}
+
+func NewControllerBuilderConfig() *BuilderConfig {
+	return &BuilderConfig{}
+}
+
+func (bc *BuilderConfig) InCluster(
+	incluster bool,
+) *BuilderConfig {
+	bc.incluster = incluster
+	return bc
+}
+
+func (bc *BuilderConfig) WithKubeClientset(
+	kubeclientset kubernetes.Interface,
+) *BuilderConfig {
+	bc.kubeclientset = kubeclientset
+	return bc
+}
+
+func (bc *BuilderConfig) WithSubmarineClientset(
+	submarineclientset clientset.Interface,
+) *BuilderConfig {
+	bc.submarineclientset = submarineclientset
+	return bc
+}
+
+func (bc *BuilderConfig) WithTraefikClientset(
+	traefikclientset traefik.Interface,
+) *BuilderConfig {
+	bc.traefikclientset = traefikclientset
+	return bc
+}
+
+func (bc *BuilderConfig) WithSubmarineInformer(
+	submarineInformer informers.SubmarineInformer,
+) *BuilderConfig {
+	bc.submarineInformer = submarineInformer
+	return bc
+}
+
+func (bc *BuilderConfig) WithNamespaceInformer(
+	namespaceInformer coreinformers.NamespaceInformer,
+) *BuilderConfig {
+	bc.namespaceInformer = namespaceInformer
+	return bc
+}
+
+func (bc *BuilderConfig) WithDeploymentInformer(
+	deploymentInformer appsinformers.DeploymentInformer,
+) *BuilderConfig {
+	bc.deploymentInformer = deploymentInformer
+	return bc
+}
+
+func (bc *BuilderConfig) WithServiceInformer(
+	serviceInformer coreinformers.ServiceInformer,
+) *BuilderConfig {
+	bc.serviceInformer = serviceInformer
+	return bc
+}
+
+func (bc *BuilderConfig) WithServiceAccountInformer(
+	serviceaccountInformer coreinformers.ServiceAccountInformer,
+) *BuilderConfig {
+	bc.serviceaccountInformer = serviceaccountInformer
+	return bc
+}
+
+func (bc *BuilderConfig) WithPersistentVolumeClaimInformer(
+	persistentvolumeclaimInformer coreinformers.PersistentVolumeClaimInformer,
+) *BuilderConfig {
+	bc.persistentvolumeclaimInformer = persistentvolumeclaimInformer
+	return bc
+}
+
+func (bc *BuilderConfig) WithIngressInformer(
+	ingressInformer extinformers.IngressInformer,
+) *BuilderConfig {
+	bc.ingressInformer = ingressInformer
+	return bc
+}
+
+func (bc *BuilderConfig) WithIngressRouteInformer(
+	ingressrouteInformer traefikinformers.IngressRouteInformer,
+) *BuilderConfig {
+	bc.ingressrouteInformer = ingressrouteInformer
+	return bc
+}
+
+func (bc *BuilderConfig) WithRoleInformer(
+	roleInformer rbacinformers.RoleInformer,
+) *BuilderConfig {
+	bc.roleInformer = roleInformer
+	return bc
+}
+
+func (bc *BuilderConfig) WithRoleBindingInformer(
+	rolebindingInformer rbacinformers.RoleBindingInformer,
+) *BuilderConfig {
+	bc.rolebindingInformer = rolebindingInformer
+	return bc
+}
diff --git a/submarine-cloud-v2/pkg/controller/controller_event_handlers.go b/submarine-cloud-v2/pkg/controller/controller_event_handlers.go
new file mode 100644
index 0000000..ff15e27
--- /dev/null
+++ b/submarine-cloud-v2/pkg/controller/controller_event_handlers.go
@@ -0,0 +1,191 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package controller
+
+import (
+	traefikv1alpha1 "github.com/traefik/traefik/v2/pkg/provider/kubernetes/crd/traefik/v1alpha1"
+	appsv1 "k8s.io/api/apps/v1"
+	corev1 "k8s.io/api/core/v1"
+	extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
+	rbacv1 "k8s.io/api/rbac/v1"
+	"k8s.io/client-go/tools/cache"
+)
+
+func (cb *ControllerBuilder) addSubmarineEventHandlers() *ControllerBuilder {
+	cb.config.submarineInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
+		AddFunc: cb.controller.enqueueSubmarine,
+		UpdateFunc: func(old, new interface{}) {
+			cb.controller.enqueueSubmarine(new)
+		},
+	})
+
+	return cb
+}
+
+func (cb *ControllerBuilder) addNamespaceEventHandlers() *ControllerBuilder {
+	cb.config.namespaceInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
+		AddFunc: cb.controller.handleObject,
+		UpdateFunc: func(old, new interface{}) {
+			newNamespace := new.(*corev1.Namespace)
+			oldNamespace := old.(*corev1.Namespace)
+			if newNamespace.ResourceVersion == oldNamespace.ResourceVersion {
+				return
+			}
+			cb.controller.handleObject(new)
+		},
+		DeleteFunc: cb.controller.handleObject,
+	})
+
+	return cb
+}
+
+func (cb *ControllerBuilder) addDeploymentEventHandlers() *ControllerBuilder {
+	cb.config.deploymentInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
+		AddFunc: cb.controller.handleObject,
+		UpdateFunc: func(old, new interface{}) {
+			newDeployment := new.(*appsv1.Deployment)
+			oldDeployment := old.(*appsv1.Deployment)
+			if newDeployment.ResourceVersion == oldDeployment.ResourceVersion {
+				return
+			}
+			cb.controller.handleObject(new)
+		},
+		DeleteFunc: cb.controller.handleObject,
+	})
+
+	return cb
+}
+
+func (cb *ControllerBuilder) addServiceEventHandlers() *ControllerBuilder {
+	cb.config.serviceInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
+		AddFunc: cb.controller.handleObject,
+		UpdateFunc: func(old, new interface{}) {
+			newService := new.(*corev1.Service)
+			oldService := old.(*corev1.Service)
+			if newService.ResourceVersion == oldService.ResourceVersion {
+				return
+			}
+			cb.controller.handleObject(new)
+		},
+		DeleteFunc: cb.controller.handleObject,
+	})
+
+	return cb
+}
+
+func (cb *ControllerBuilder) addServiceAccountEventHandlers() *ControllerBuilder {
+	cb.config.serviceaccountInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
+		AddFunc: cb.controller.handleObject,
+		UpdateFunc: func(old, new interface{}) {
+			newServiceAccount := new.(*corev1.ServiceAccount)
+			oldServiceAccount := old.(*corev1.ServiceAccount)
+			if newServiceAccount.ResourceVersion == oldServiceAccount.ResourceVersion {
+				return
+			}
+			cb.controller.handleObject(new)
+		},
+		DeleteFunc: cb.controller.handleObject,
+	})
+
+	return cb
+}
+
+func (cb *ControllerBuilder) addPersistentVolumeClaimEventHandlers() *ControllerBuilder {
+	cb.config.persistentvolumeclaimInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
+		AddFunc: cb.controller.handleObject,
+		UpdateFunc: func(old, new interface{}) {
+			newPVC := new.(*corev1.PersistentVolumeClaim)
+			oldPVC := old.(*corev1.PersistentVolumeClaim)
+			if newPVC.ResourceVersion == oldPVC.ResourceVersion {
+				return
+			}
+			cb.controller.handleObject(new)
+		},
+		DeleteFunc: cb.controller.handleObject,
+	})
+
+	return cb
+}
+
+func (cb *ControllerBuilder) addIngressEventHandlers() *ControllerBuilder {
+	cb.config.ingressInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
+		AddFunc: cb.controller.handleObject,
+		UpdateFunc: func(old, new interface{}) {
+			newIngress := new.(*extensionsv1beta1.Ingress)
+			oldIngress := old.(*extensionsv1beta1.Ingress)
+			if newIngress.ResourceVersion == oldIngress.ResourceVersion {
+				return
+			}
+			cb.controller.handleObject(new)
+		},
+		DeleteFunc: cb.controller.handleObject,
+	})
+
+	return cb
+}
+
+func (cb *ControllerBuilder) addIngressRouteEventHandlers() *ControllerBuilder {
+	cb.config.ingressrouteInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
+		AddFunc: cb.controller.handleObject,
+		UpdateFunc: func(old, new interface{}) {
+			newIngressRoute := new.(*traefikv1alpha1.IngressRoute)
+			oldIngressRoute := old.(*traefikv1alpha1.IngressRoute)
+			if newIngressRoute.ResourceVersion == oldIngressRoute.ResourceVersion {
+				return
+			}
+			cb.controller.handleObject(new)
+		},
+		DeleteFunc: cb.controller.handleObject,
+	})
+
+	return cb
+}
+
+func (cb *ControllerBuilder) addRoleEventHandlers() *ControllerBuilder {
+	cb.config.roleInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
+		AddFunc: cb.controller.handleObject,
+		UpdateFunc: func(old, new interface{}) {
+			newRole := new.(*rbacv1.Role)
+			oldRole := old.(*rbacv1.Role)
+			if newRole.ResourceVersion == oldRole.ResourceVersion {
+				return
+			}
+			cb.controller.handleObject(new)
+		},
+		DeleteFunc: cb.controller.handleObject,
+	})
+
+	return cb
+}
+
+func (cb *ControllerBuilder) addRoleBindingEventHandlers() *ControllerBuilder {
+	cb.config.rolebindingInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
+		AddFunc: cb.controller.handleObject,
+		UpdateFunc: func(old, new interface{}) {
+			newRoleBinding := new.(*rbacv1.RoleBinding)
+			oldRoleBinding := old.(*rbacv1.RoleBinding)
+			if newRoleBinding.ResourceVersion == oldRoleBinding.ResourceVersion {
+				return
+			}
+			cb.controller.handleObject(new)
+		},
+		DeleteFunc: cb.controller.handleObject,
+	})
+
+	return cb
+}

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@submarine.apache.org
For additional commands, e-mail: dev-help@submarine.apache.org