From e705b54202c33a742e33bcf9d36c2cf42e9407e7 Mon Sep 17 00:00:00 2001 From: Shoubhik Bose Date: Fri, 10 May 2019 01:03:33 -0400 Subject: [PATCH 1/7] Deploy the App Topology REST service and watch it --- pkg/controller/add_topology.go | 8 ++ pkg/service/topology/doc.go | 1 + pkg/service/topology/topology.go | 170 +++++++++++++++++++++++++++++++ 3 files changed, 179 insertions(+) create mode 100644 pkg/controller/add_topology.go create mode 100644 pkg/service/topology/doc.go create mode 100644 pkg/service/topology/topology.go diff --git a/pkg/controller/add_topology.go b/pkg/controller/add_topology.go new file mode 100644 index 0000000..a93aa04 --- /dev/null +++ b/pkg/controller/add_topology.go @@ -0,0 +1,8 @@ +package controller + +import "github.com/redhat-developer/devconsole-operator/pkg/service/topology" + +func init() { + // AddToManagerFuncs is a list of functions to create controllers and add them to a manager. + AddToManagerFuncs = append(AddToManagerFuncs, topology.Add) +} diff --git a/pkg/service/topology/doc.go b/pkg/service/topology/doc.go new file mode 100644 index 0000000..18f8a87 --- /dev/null +++ b/pkg/service/topology/doc.go @@ -0,0 +1 @@ +package topology diff --git a/pkg/service/topology/topology.go b/pkg/service/topology/topology.go new file mode 100644 index 0000000..6d71abd --- /dev/null +++ b/pkg/service/topology/topology.go @@ -0,0 +1,170 @@ +package topology + +import ( + "fmt" + + v1 "github.com/openshift/api/apps/v1" + deploymentconfig "github.com/openshift/client-go/apps/clientset/versioned/typed/apps/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller" + "sigs.k8s.io/controller-runtime/pkg/event" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/manager" + "sigs.k8s.io/controller-runtime/pkg/predicate" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + logf "sigs.k8s.io/controller-runtime/pkg/runtime/log" + "sigs.k8s.io/controller-runtime/pkg/source" +) + +var log = logf.Log + +// ReconcileService reconciles a Component object +type ReconcileService struct { + // This client, initialized using mgr.Client() above, is a split client + // that reads objects from the cache and writes to the apiserver + client client.Client + scheme *runtime.Scheme +} + +// Add creates a new Component Controller and adds it to the Manager. The Manager will set fields on the Controller +// and Start it when the Manager is Started. +func Add(mgr manager.Manager) error { + return add(mgr, newReconciler(mgr)) +} + +const ( + // ServicesNamespace is the name of the namespace where this operator would install the Rest Service + ServicesNamespace = "openshift-operators" // move this out to env var ? + + // ServiceName is the name that would be assigned to all objects associated with the Rest Service + ServiceName = "devconsole-app" // move this out to env var ? +) + +// newReconciler returns a new reconcile.Reconciler +func newReconciler(mgr manager.Manager) reconcile.Reconciler { + config := mgr.GetConfig() + + dcClient, _ := deploymentconfig.NewForConfig(config) + + // Check if DC already exists + existingDC, _ := dcClient.DeploymentConfigs(ServicesNamespace).Get(ServiceName, metav1.GetOptions{}) + + if existingDC.Name == ServiceName { + return &ReconcileService{client: mgr.GetClient(), scheme: mgr.GetScheme()} + } + + // If DC didn't exist, then we can skip + _, err := dcClient.DeploymentConfigs(ServicesNamespace).Create(newDeploymentConfigForAppService(nil, ServiceName, ServicesNamespace)) + if err != nil { + fmt.Println(err) // Log.Error(..) + } + + // TODO: Create a service, if absent + + // TODO: Create a route, if absent + + return &ReconcileService{client: mgr.GetClient(), scheme: mgr.GetScheme()} +} + +// add adds a new Controller to mgr with r as the reconcile.Reconciler +func add(mgr manager.Manager, r reconcile.Reconciler) error { + + // Create a new controller + c, err := controller.New("topology-controller", mgr, controller.Options{Reconciler: r}) + if err != nil { + return err + } + + // Do not send events which are related to the specific DC. + pred := predicate.Funcs{ + // TODO: When the deployment is being created, DC gets updated + // and that would trigget this. How do we filter out such events? + UpdateFunc: func(e event.UpdateEvent) bool { + return e.MetaOld.GetName() == ServiceName && e.MetaOld.GetNamespace() == ServicesNamespace + }, + + // TODO: In all probability, any delele event is interesting to us. + DeleteFunc: func(e event.DeleteEvent) bool { + return e.Meta.GetName() == ServiceName && e.Meta.GetNamespace() == ServicesNamespace + }, + + // TODO: When a new one is created because an operator is being deployed.. + CreateFunc: func(e event.CreateEvent) bool { + return false + }, + } + + err = c.Watch(&source.Kind{Type: &v1.DeploymentConfig{}}, &handler.EnqueueRequestForObject{}, pred) + if err != nil { + return err + } + return nil +} + +// Reconcile handles events related to changes to the App Topology Service deployment. +// This includes events from service/route/dc named "ServiceName" in the namespace "ServiceNameSpace" +func (r *ReconcileService) Reconcile(request reconcile.Request) (reconcile.Result, error) { + // TODO: Watch changes to the DeploymentConfig ( .. and the service, and the route ) + // something happened to the specific DC, please react.. + return reconcile.Result{}, nil +} + +func newDeploymentConfigForAppService(containerPorts []corev1.ContainerPort, serviceName string, serviceNameSpace string) *v1.DeploymentConfig { + labels := getLabelsForServiceDeployments(ServiceName) + //annotations := resource.GetAnnotationsForCR(cp) + if containerPorts == nil { + containerPorts = []corev1.ContainerPort{{ + ContainerPort: 8080, + Protocol: corev1.ProtocolTCP, + }} + } + return &v1.DeploymentConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: ServiceName, + Namespace: ServicesNamespace, + Labels: labels, + // Annotations: annotations, + }, + Spec: v1.DeploymentConfigSpec{ + Strategy: v1.DeploymentStrategy{ + Type: v1.DeploymentStrategyTypeRecreate, + }, + Replicas: 1, + Selector: labels, + Template: &corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Name: ServiceName, + Namespace: ServicesNamespace, + Labels: labels, + // Annotations: annotations, + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: ServiceName, + Image: "quay.io/redhat-developer/app-service:latest", // parameterize this + Ports: containerPorts, + }, + }, + }, + }, + Triggers: []v1.DeploymentTriggerPolicy{ + { + Type: v1.DeploymentTriggerOnConfigChange, + }, + }, + }, + } +} + +func getLabelsForServiceDeployments(serviceName string) map[string]string { + labels := make(map[string]string) + labels["app.kubernetes.io/name"] = serviceName + labels["app"] = serviceName + + return labels +} From d6e6b7b0268083bb55cf954ee8ea204243b15f34 Mon Sep 17 00:00:00 2001 From: Shoubhik Bose Date: Fri, 10 May 2019 01:18:52 -0400 Subject: [PATCH 2/7] route is not needed --- pkg/service/topology/topology.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pkg/service/topology/topology.go b/pkg/service/topology/topology.go index 6d71abd..2dfcac6 100644 --- a/pkg/service/topology/topology.go +++ b/pkg/service/topology/topology.go @@ -65,8 +65,6 @@ func newReconciler(mgr manager.Manager) reconcile.Reconciler { // TODO: Create a service, if absent - // TODO: Create a route, if absent - return &ReconcileService{client: mgr.GetClient(), scheme: mgr.GetScheme()} } @@ -106,7 +104,7 @@ func add(mgr manager.Manager, r reconcile.Reconciler) error { } // Reconcile handles events related to changes to the App Topology Service deployment. -// This includes events from service/route/dc named "ServiceName" in the namespace "ServiceNameSpace" +// This includes events from service/dc named "ServiceName" in the namespace "ServiceNameSpace" func (r *ReconcileService) Reconcile(request reconcile.Request) (reconcile.Result, error) { // TODO: Watch changes to the DeploymentConfig ( .. and the service, and the route ) // something happened to the specific DC, please react.. From 2be009fb76ffcbe6785266d721e76c754cf74bd3 Mon Sep 17 00:00:00 2001 From: Shoubhik Bose Date: Fri, 10 May 2019 01:33:11 -0400 Subject: [PATCH 3/7] keep lint happy --- pkg/service/topology/topology.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/pkg/service/topology/topology.go b/pkg/service/topology/topology.go index 2dfcac6..e1a6c66 100644 --- a/pkg/service/topology/topology.go +++ b/pkg/service/topology/topology.go @@ -16,12 +16,9 @@ import ( "sigs.k8s.io/controller-runtime/pkg/predicate" "sigs.k8s.io/controller-runtime/pkg/reconcile" - logf "sigs.k8s.io/controller-runtime/pkg/runtime/log" "sigs.k8s.io/controller-runtime/pkg/source" ) -var log = logf.Log - // ReconcileService reconciles a Component object type ReconcileService struct { // This client, initialized using mgr.Client() above, is a split client From b5dd08240dc9953ef6db362f586ff028f7cc4a6c Mon Sep 17 00:00:00 2001 From: akashshinde Date: Mon, 13 May 2019 17:21:06 +0530 Subject: [PATCH 4/7] Add controller to deploy topology service on operator deploy --- pkg/controller/add_topology.go | 2 +- .../component/component_controller.go | 1 + pkg/{ => controller}/service/topology/doc.go | 0 .../service/topology/topology_controller.go} | 144 +++++++++++++----- 4 files changed, 112 insertions(+), 35 deletions(-) rename pkg/{ => controller}/service/topology/doc.go (100%) rename pkg/{service/topology/topology.go => controller/service/topology/topology_controller.go} (50%) diff --git a/pkg/controller/add_topology.go b/pkg/controller/add_topology.go index a93aa04..57f4eb9 100644 --- a/pkg/controller/add_topology.go +++ b/pkg/controller/add_topology.go @@ -1,6 +1,6 @@ package controller -import "github.com/redhat-developer/devconsole-operator/pkg/service/topology" +import "github.com/redhat-developer/devconsole-operator/pkg/controller/service/topology" func init() { // AddToManagerFuncs is a list of functions to create controllers and add them to a manager. diff --git a/pkg/controller/component/component_controller.go b/pkg/controller/component/component_controller.go index d3c0424..7981357 100644 --- a/pkg/controller/component/component_controller.go +++ b/pkg/controller/component/component_controller.go @@ -4,6 +4,7 @@ import ( "context" e "errors" "fmt" + v1 "github.com/openshift/api/apps/v1" buildv1 "github.com/openshift/api/build/v1" imagev1 "github.com/openshift/api/image/v1" diff --git a/pkg/service/topology/doc.go b/pkg/controller/service/topology/doc.go similarity index 100% rename from pkg/service/topology/doc.go rename to pkg/controller/service/topology/doc.go diff --git a/pkg/service/topology/topology.go b/pkg/controller/service/topology/topology_controller.go similarity index 50% rename from pkg/service/topology/topology.go rename to pkg/controller/service/topology/topology_controller.go index e1a6c66..3309da7 100644 --- a/pkg/service/topology/topology.go +++ b/pkg/controller/service/topology/topology_controller.go @@ -3,11 +3,12 @@ package topology import ( "fmt" - v1 "github.com/openshift/api/apps/v1" - deploymentconfig "github.com/openshift/client-go/apps/clientset/versioned/typed/apps/v1" + appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/intstr" + "k8s.io/client-go/kubernetes" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller" "sigs.k8s.io/controller-runtime/pkg/event" @@ -16,15 +17,21 @@ import ( "sigs.k8s.io/controller-runtime/pkg/predicate" "sigs.k8s.io/controller-runtime/pkg/reconcile" + logf "sigs.k8s.io/controller-runtime/pkg/runtime/log" "sigs.k8s.io/controller-runtime/pkg/source" ) +var log = logf.Log + // ReconcileService reconciles a Component object type ReconcileService struct { // This client, initialized using mgr.Client() above, is a split client // that reads objects from the cache and writes to the apiserver client client.Client scheme *runtime.Scheme + + // coreClient is kubernetes go client which gets intialized using mgr.Config(). + coreClient *kubernetes.Clientset } // Add creates a new Component Controller and adds it to the Manager. The Manager will set fields on the Controller @@ -35,7 +42,7 @@ func Add(mgr manager.Manager) error { const ( // ServicesNamespace is the name of the namespace where this operator would install the Rest Service - ServicesNamespace = "openshift-operators" // move this out to env var ? + ServicesNamespace = "akash-operators" // move this out to env var ? // ServiceName is the name that would be assigned to all objects associated with the Rest Service ServiceName = "devconsole-app" // move this out to env var ? @@ -43,26 +50,35 @@ const ( // newReconciler returns a new reconcile.Reconciler func newReconciler(mgr manager.Manager) reconcile.Reconciler { - config := mgr.GetConfig() - - dcClient, _ := deploymentconfig.NewForConfig(config) - // Check if DC already exists - existingDC, _ := dcClient.DeploymentConfigs(ServicesNamespace).Get(ServiceName, metav1.GetOptions{}) - - if existingDC.Name == ServiceName { - return &ReconcileService{client: mgr.GetClient(), scheme: mgr.GetScheme()} + // Initialize kubernetes client + cl, err := kubernetes.NewForConfig(mgr.GetConfig()) + if err != nil { + log.Error(err, "Failed to create rest client") + return &ReconcileService{client: mgr.GetClient(), scheme: mgr.GetScheme(), coreClient: cl} } - // If DC didn't exist, then we can skip - _, err := dcClient.DeploymentConfigs(ServicesNamespace).Create(newDeploymentConfigForAppService(nil, ServiceName, ServicesNamespace)) + // Check if Deployment already exists + _, err = cl.AppsV1().Deployments(ServicesNamespace).Get(ServiceName, metav1.GetOptions{}) if err != nil { - fmt.Println(err) // Log.Error(..) + // If Deployment didn't exist, then we need to create one. + _, err = cl.AppsV1().Deployments(ServicesNamespace).Create(newDeploymentConfigForAppService(nil, ServiceName, ServicesNamespace)) + if err != nil { + log.Error(err, "Failed to create deployment") + } } - // TODO: Create a service, if absent + // Moving ahead to create service assuming deployment is created succesfully. + _, err = cl.CoreV1().Services(ServicesNamespace).Get(ServiceName, metav1.GetOptions{}) + if err != nil { + svc, _ := newService(8080) + _, err = cl.CoreV1().Services(ServicesNamespace).Create(svc) + if err != nil { + log.Error(err, "Failed to create service") + } + } - return &ReconcileService{client: mgr.GetClient(), scheme: mgr.GetScheme()} + return &ReconcileService{client: mgr.GetClient(), scheme: mgr.GetScheme(), coreClient: cl} } // add adds a new Controller to mgr with r as the reconcile.Reconciler @@ -93,7 +109,14 @@ func add(mgr manager.Manager, r reconcile.Reconciler) error { }, } - err = c.Watch(&source.Kind{Type: &v1.DeploymentConfig{}}, &handler.EnqueueRequestForObject{}, pred) + // Watch for Deployment Update and Delete event + err = c.Watch(&source.Kind{Type: &appsv1.Deployment{}}, &handler.EnqueueRequestForObject{}, pred) + if err != nil { + return err + } + + // Watch for Service Update and Delete event + err = c.Watch(&source.Kind{Type: &corev1.Service{}}, &handler.EnqueueRequestForObject{}, pred) if err != nil { return err } @@ -103,12 +126,39 @@ func add(mgr manager.Manager, r reconcile.Reconciler) error { // Reconcile handles events related to changes to the App Topology Service deployment. // This includes events from service/dc named "ServiceName" in the namespace "ServiceNameSpace" func (r *ReconcileService) Reconcile(request reconcile.Request) (reconcile.Result, error) { - // TODO: Watch changes to the DeploymentConfig ( .. and the service, and the route ) - // something happened to the specific DC, please react.. + // Check if deployment exist or not, create one is if absent + dExist, err := r.coreClient.AppsV1().Deployments(ServicesNamespace).Get(ServiceName, metav1.GetOptions{}) + if dExist.Name == ServiceName { + log.Info("Deployment already exist with the name : %s", dExist.Name) + } + if err != nil { + _, err = r.coreClient.AppsV1().Deployments(ServicesNamespace).Create(newDeploymentConfigForAppService(nil, ServiceName, ServicesNamespace)) + if err != nil { + log.Error(err, "Failed to redeploy deployment") + return reconcile.Result{}, err + } + } + + // Check if service exist or not, create one is if absent + svcExist, err := r.coreClient.CoreV1().Services(ServicesNamespace).Get(ServiceName, metav1.GetOptions{}) + if svcExist.Name == ServiceName { + return reconcile.Result{}, err + } + if err != nil { + newSvc, err := newService(8080) + if err != nil { + return reconcile.Result{}, err + } + _, err = r.coreClient.CoreV1().Services(ServicesNamespace).Create(newSvc) + if err != nil { + fmt.Println("Failed to redeploy dc") + } + } + return reconcile.Result{}, nil } -func newDeploymentConfigForAppService(containerPorts []corev1.ContainerPort, serviceName string, serviceNameSpace string) *v1.DeploymentConfig { +func newDeploymentConfigForAppService(containerPorts []corev1.ContainerPort, serviceName string, serviceNameSpace string) *appsv1.Deployment { labels := getLabelsForServiceDeployments(ServiceName) //annotations := resource.GetAnnotationsForCR(cp) if containerPorts == nil { @@ -117,43 +167,67 @@ func newDeploymentConfigForAppService(containerPorts []corev1.ContainerPort, ser Protocol: corev1.ProtocolTCP, }} } - return &v1.DeploymentConfig{ + return &appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{ Name: ServiceName, Namespace: ServicesNamespace, Labels: labels, - // Annotations: annotations, }, - Spec: v1.DeploymentConfigSpec{ - Strategy: v1.DeploymentStrategy{ - Type: v1.DeploymentStrategyTypeRecreate, + Spec: appsv1.DeploymentSpec{ + Strategy: appsv1.DeploymentStrategy{ + Type: "Recreate", + }, + Replicas: int32Ptr(1), + Selector: &metav1.LabelSelector{ + MatchLabels: labels, }, - Replicas: 1, - Selector: labels, - Template: &corev1.PodTemplateSpec{ + Template: corev1.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ Name: ServiceName, Namespace: ServicesNamespace, Labels: labels, - // Annotations: annotations, }, Spec: corev1.PodSpec{ Containers: []corev1.Container{ { Name: ServiceName, - Image: "quay.io/redhat-developer/app-service:latest", // parameterize this + Image: "quay.io/redhat-developer/app-service:latest", // TODO(Akash): parameterize this Ports: containerPorts, }, }, }, }, - Triggers: []v1.DeploymentTriggerPolicy{ - { - Type: v1.DeploymentTriggerOnConfigChange, - }, + }, + } +} + +func newService(port int32) (*corev1.Service, error) { + labels := getLabelsForServiceDeployments(ServiceName) + if port > 65536 || port < 1024 { + return nil, fmt.Errorf("port %d is out of range [1024-65535]", port) + } + var svcPorts []corev1.ServicePort + svcPort := corev1.ServicePort{ + Name: ServiceName + "-tcp", + Port: port, + Protocol: corev1.ProtocolTCP, + TargetPort: intstr.FromInt(int(port)), + } + svcPorts = append(svcPorts, svcPort) + svc := &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: ServiceName, + Namespace: ServicesNamespace, + Labels: labels, + }, + Spec: corev1.ServiceSpec{ + Ports: svcPorts, + Selector: map[string]string{ + "deploymentconfig": ServiceName, }, }, } + return svc, nil } func getLabelsForServiceDeployments(serviceName string) map[string]string { @@ -163,3 +237,5 @@ func getLabelsForServiceDeployments(serviceName string) map[string]string { return labels } + +func int32Ptr(i int32) *int32 { return &i } From ed70ea06b55f9173d979ed7af393104ea3962501 Mon Sep 17 00:00:00 2001 From: akashshinde Date: Mon, 13 May 2019 17:22:53 +0530 Subject: [PATCH 5/7] Update namespace --- pkg/controller/service/topology/topology_controller.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/controller/service/topology/topology_controller.go b/pkg/controller/service/topology/topology_controller.go index 3309da7..27ebdd0 100644 --- a/pkg/controller/service/topology/topology_controller.go +++ b/pkg/controller/service/topology/topology_controller.go @@ -42,7 +42,7 @@ func Add(mgr manager.Manager) error { const ( // ServicesNamespace is the name of the namespace where this operator would install the Rest Service - ServicesNamespace = "akash-operators" // move this out to env var ? + ServicesNamespace = "openshift-operators" // move this out to env var ? // ServiceName is the name that would be assigned to all objects associated with the Rest Service ServiceName = "devconsole-app" // move this out to env var ? From 83ac8d94c75dab34341d3689347ac66bc6273591 Mon Sep 17 00:00:00 2001 From: akashshinde Date: Mon, 13 May 2019 17:29:28 +0530 Subject: [PATCH 6/7] Fix typo --- pkg/controller/service/topology/topology_controller.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/controller/service/topology/topology_controller.go b/pkg/controller/service/topology/topology_controller.go index 27ebdd0..ab6cf41 100644 --- a/pkg/controller/service/topology/topology_controller.go +++ b/pkg/controller/service/topology/topology_controller.go @@ -126,7 +126,7 @@ func add(mgr manager.Manager, r reconcile.Reconciler) error { // Reconcile handles events related to changes to the App Topology Service deployment. // This includes events from service/dc named "ServiceName" in the namespace "ServiceNameSpace" func (r *ReconcileService) Reconcile(request reconcile.Request) (reconcile.Result, error) { - // Check if deployment exist or not, create one is if absent + // Check if deployment exist or not, create one if absent dExist, err := r.coreClient.AppsV1().Deployments(ServicesNamespace).Get(ServiceName, metav1.GetOptions{}) if dExist.Name == ServiceName { log.Info("Deployment already exist with the name : %s", dExist.Name) @@ -139,7 +139,7 @@ func (r *ReconcileService) Reconcile(request reconcile.Request) (reconcile.Resul } } - // Check if service exist or not, create one is if absent + // Check if service exist or not, create one if absent svcExist, err := r.coreClient.CoreV1().Services(ServicesNamespace).Get(ServiceName, metav1.GetOptions{}) if svcExist.Name == ServiceName { return reconcile.Result{}, err From e3df5c4a3e2a6bbee1c28f9b13b1572c4453c64b Mon Sep 17 00:00:00 2001 From: akashshinde Date: Thu, 16 May 2019 12:48:10 +0530 Subject: [PATCH 7/7] Add topology controller unit tests --- .../service/topology/topology_controller.go | 39 ++++++++------- .../topology/topology_controller_test.go | 49 +++++++++++++++++++ 2 files changed, 71 insertions(+), 17 deletions(-) create mode 100644 pkg/controller/service/topology/topology_controller_test.go diff --git a/pkg/controller/service/topology/topology_controller.go b/pkg/controller/service/topology/topology_controller.go index ab6cf41..862a332 100644 --- a/pkg/controller/service/topology/topology_controller.go +++ b/pkg/controller/service/topology/topology_controller.go @@ -9,6 +9,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/client-go/kubernetes" + "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller" "sigs.k8s.io/controller-runtime/pkg/event" @@ -16,7 +17,6 @@ import ( "sigs.k8s.io/controller-runtime/pkg/manager" "sigs.k8s.io/controller-runtime/pkg/predicate" "sigs.k8s.io/controller-runtime/pkg/reconcile" - logf "sigs.k8s.io/controller-runtime/pkg/runtime/log" "sigs.k8s.io/controller-runtime/pkg/source" ) @@ -31,7 +31,10 @@ type ReconcileService struct { scheme *runtime.Scheme // coreClient is kubernetes go client which gets intialized using mgr.Config(). - coreClient *kubernetes.Clientset + coreClient kubernetes.Interface + + DeploymentName string + DeploymentNamespace string } // Add creates a new Component Controller and adds it to the Manager. The Manager will set fields on the Controller @@ -71,7 +74,7 @@ func newReconciler(mgr manager.Manager) reconcile.Reconciler { // Moving ahead to create service assuming deployment is created succesfully. _, err = cl.CoreV1().Services(ServicesNamespace).Get(ServiceName, metav1.GetOptions{}) if err != nil { - svc, _ := newService(8080) + svc, _ := newService(ServicesNamespace, ServiceName, 8080) _, err = cl.CoreV1().Services(ServicesNamespace).Create(svc) if err != nil { log.Error(err, "Failed to create service") @@ -127,29 +130,31 @@ func add(mgr manager.Manager, r reconcile.Reconciler) error { // This includes events from service/dc named "ServiceName" in the namespace "ServiceNameSpace" func (r *ReconcileService) Reconcile(request reconcile.Request) (reconcile.Result, error) { // Check if deployment exist or not, create one if absent - dExist, err := r.coreClient.AppsV1().Deployments(ServicesNamespace).Get(ServiceName, metav1.GetOptions{}) - if dExist.Name == ServiceName { - log.Info("Deployment already exist with the name : %s", dExist.Name) - } + namespace := request.Namespace + name := request.Name + dExist, err := r.coreClient.AppsV1().Deployments(namespace).Get(name, metav1.GetOptions{}) if err != nil { - _, err = r.coreClient.AppsV1().Deployments(ServicesNamespace).Create(newDeploymentConfigForAppService(nil, ServiceName, ServicesNamespace)) + _, err = r.coreClient.AppsV1().Deployments(namespace).Create(newDeploymentConfigForAppService(nil, name, namespace)) if err != nil { log.Error(err, "Failed to redeploy deployment") return reconcile.Result{}, err } } + if dExist != nil && dExist.Name == name { + log.Info("Deployment already exist with the name : %s", dExist.Name) + } // Check if service exist or not, create one if absent - svcExist, err := r.coreClient.CoreV1().Services(ServicesNamespace).Get(ServiceName, metav1.GetOptions{}) - if svcExist.Name == ServiceName { + svcExist, err := r.coreClient.CoreV1().Services(namespace).Get(name, metav1.GetOptions{}) + if svcExist != nil && svcExist.Name == name { return reconcile.Result{}, err } if err != nil { - newSvc, err := newService(8080) + newSvc, err := newService(namespace, name, 8080) if err != nil { return reconcile.Result{}, err } - _, err = r.coreClient.CoreV1().Services(ServicesNamespace).Create(newSvc) + _, err = r.coreClient.CoreV1().Services(namespace).Create(newSvc) if err != nil { fmt.Println("Failed to redeploy dc") } @@ -201,14 +206,14 @@ func newDeploymentConfigForAppService(containerPorts []corev1.ContainerPort, ser } } -func newService(port int32) (*corev1.Service, error) { +func newService(namespace, name string, port int32) (*corev1.Service, error) { labels := getLabelsForServiceDeployments(ServiceName) if port > 65536 || port < 1024 { return nil, fmt.Errorf("port %d is out of range [1024-65535]", port) } var svcPorts []corev1.ServicePort svcPort := corev1.ServicePort{ - Name: ServiceName + "-tcp", + Name: name + "-tcp", Port: port, Protocol: corev1.ProtocolTCP, TargetPort: intstr.FromInt(int(port)), @@ -216,14 +221,14 @@ func newService(port int32) (*corev1.Service, error) { svcPorts = append(svcPorts, svcPort) svc := &corev1.Service{ ObjectMeta: metav1.ObjectMeta{ - Name: ServiceName, - Namespace: ServicesNamespace, + Name: name, + Namespace: namespace, Labels: labels, }, Spec: corev1.ServiceSpec{ Ports: svcPorts, Selector: map[string]string{ - "deploymentconfig": ServiceName, + "deploymentconfig": name, }, }, } diff --git a/pkg/controller/service/topology/topology_controller_test.go b/pkg/controller/service/topology/topology_controller_test.go new file mode 100644 index 0000000..8cd9312 --- /dev/null +++ b/pkg/controller/service/topology/topology_controller_test.go @@ -0,0 +1,49 @@ +package topology + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + coreFakeClient "k8s.io/client-go/kubernetes/fake" + "k8s.io/client-go/kubernetes/scheme" + + "sigs.k8s.io/controller-runtime/pkg/client/fake" + "sigs.k8s.io/controller-runtime/pkg/reconcile" +) + +func TestReconcile(t *testing.T) { + t.Run("Check if Deployment and Services are getting created", func(t *testing.T) { + objs := []runtime.Object{} + cl := fake.NewFakeClient(objs...) + s := scheme.Scheme + coreFakeC := coreFakeClient.NewSimpleClientset(objs...) + r := ReconcileService{ + client: cl, + coreClient: coreFakeC, + scheme: s, + } + _, err := r.Reconcile(reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: ServiceName, + Namespace: ServicesNamespace, + }, + }) + if err != nil { + t.Log(err) + t.Fail() + } + assert.Nil(t, err, "Reconcile failed with error ") + deployment, _ := r.coreClient.AppsV1().Deployments(ServicesNamespace).Get(ServiceName, metav1.GetOptions{}) + assert.NotNil(t, deployment, "Deployment should have created") + assert.Equal(t, ServiceName, deployment.Name, "Deployment is not created with name "+ServiceName) + assert.Equal(t, ServicesNamespace, deployment.Namespace, "Deployment is not created in expected namespace"+ServicesNamespace) + service, _ := r.coreClient.CoreV1().Services(ServicesNamespace).Get(ServiceName, metav1.GetOptions{}) + assert.NotNil(t, service, "Service should have created") + assert.Equal(t, ServiceName, deployment.Name, "Service is not created with name "+ServiceName) + assert.Equal(t, ServicesNamespace, deployment.Namespace, "Service is not created in namespace "+ServicesNamespace) + }) +}