github.com/florinpeter/operator-sdk@v0.8.2/example/memcached-operator/memcached_controller.go.tmpl (about) 1 package memcached 2 3 import ( 4 "context" 5 "reflect" 6 7 cachev1alpha1 "github.com/example-inc/memcached-operator/pkg/apis/cache/v1alpha1" 8 9 appsv1 "k8s.io/api/apps/v1" 10 corev1 "k8s.io/api/core/v1" 11 "k8s.io/apimachinery/pkg/api/errors" 12 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 13 "k8s.io/apimachinery/pkg/labels" 14 "k8s.io/apimachinery/pkg/runtime" 15 "k8s.io/apimachinery/pkg/types" 16 "sigs.k8s.io/controller-runtime/pkg/client" 17 "sigs.k8s.io/controller-runtime/pkg/controller" 18 "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" 19 "sigs.k8s.io/controller-runtime/pkg/handler" 20 "sigs.k8s.io/controller-runtime/pkg/manager" 21 "sigs.k8s.io/controller-runtime/pkg/reconcile" 22 logf "sigs.k8s.io/controller-runtime/pkg/runtime/log" 23 "sigs.k8s.io/controller-runtime/pkg/source" 24 ) 25 26 var log = logf.Log.WithName("controller_memcached") 27 28 /** 29 * USER ACTION REQUIRED: This is a scaffold file intended for the user to modify with their own Controller 30 * business logic. Delete these comments after modifying this file.* 31 */ 32 33 // Add creates a new Memcached Controller and adds it to the Manager. The Manager will set fields on the Controller 34 // and Start it when the Manager is Started. 35 func Add(mgr manager.Manager) error { 36 return add(mgr, newReconciler(mgr)) 37 } 38 39 // newReconciler returns a new reconcile.Reconciler 40 func newReconciler(mgr manager.Manager) reconcile.Reconciler { 41 return &ReconcileMemcached{client: mgr.GetClient(), scheme: mgr.GetScheme()} 42 } 43 44 // add adds a new Controller to mgr with r as the reconcile.Reconciler 45 func add(mgr manager.Manager, r reconcile.Reconciler) error { 46 // Create a new controller 47 c, err := controller.New("memcached-controller", mgr, controller.Options{Reconciler: r}) 48 if err != nil { 49 return err 50 } 51 52 // Watch for changes to primary resource Memcached 53 err = c.Watch(&source.Kind{Type: &cachev1alpha1.Memcached{}}, &handler.EnqueueRequestForObject{}) 54 if err != nil { 55 return err 56 } 57 58 // TODO(user): Modify this to be the types you create that are owned by the primary resource 59 // Watch for changes to secondary resource Pods and requeue the owner Memcached 60 err = c.Watch(&source.Kind{Type: &appsv1.Deployment{}}, &handler.EnqueueRequestForOwner{ 61 IsController: true, 62 OwnerType: &cachev1alpha1.Memcached{}, 63 }) 64 if err != nil { 65 return err 66 } 67 68 return nil 69 } 70 71 // blank assignment to verify that ReconcileMemcached implements reconcile.Reconciler 72 var _ reconcile.Reconciler = &ReconcileMemcached{} 73 74 // ReconcileMemcached reconciles a Memcached object 75 type ReconcileMemcached struct { 76 // TODO: Clarify the split client 77 // This client, initialized using mgr.Client() above, is a split client 78 // that reads objects from the cache and writes to the apiserver 79 client client.Client 80 scheme *runtime.Scheme 81 } 82 83 // Reconcile reads that state of the cluster for a Memcached object and makes changes based on the state read 84 // and what is in the Memcached.Spec 85 // TODO(user): Modify this Reconcile function to implement your Controller logic. This example creates 86 // a Memcached Deployment for each Memcached CR 87 // Note: 88 // The Controller will requeue the Request to be processed again if the returned error is non-nil or 89 // Result.Requeue is true, otherwise upon completion it will remove the work from the queue. 90 func (r *ReconcileMemcached) Reconcile(request reconcile.Request) (reconcile.Result, error) { 91 reqLogger := log.WithValues("Request.Namespace", request.Namespace, "Request.Name", request.Name) 92 reqLogger.Info("Reconciling Memcached") 93 94 // Fetch the Memcached instance 95 memcached := &cachev1alpha1.Memcached{} 96 err := r.client.Get(context.TODO(), request.NamespacedName, memcached) 97 if err != nil { 98 if errors.IsNotFound(err) { 99 // Request object not found, could have been deleted after reconcile request. 100 // Owned objects are automatically garbage collected. For additional cleanup logic use finalizers. 101 // Return and don't requeue 102 reqLogger.Info("Memcached resource not found. Ignoring since object must be deleted") 103 return reconcile.Result{}, nil 104 } 105 // Error reading the object - requeue the request. 106 reqLogger.Error(err, "Failed to get Memcached") 107 return reconcile.Result{}, err 108 } 109 110 // Check if the deployment already exists, if not create a new one 111 found := &appsv1.Deployment{} 112 err = r.client.Get(context.TODO(), types.NamespacedName{Name: memcached.Name, Namespace: memcached.Namespace}, found) 113 if err != nil && errors.IsNotFound(err) { 114 // Define a new deployment 115 dep := r.deploymentForMemcached(memcached) 116 reqLogger.Info("Creating a new Deployment", "Deployment.Namespace", dep.Namespace, "Deployment.Name", dep.Name) 117 err = r.client.Create(context.TODO(), dep) 118 if err != nil { 119 reqLogger.Error(err, "Failed to create new Deployment", "Deployment.Namespace", dep.Namespace, "Deployment.Name", dep.Name) 120 return reconcile.Result{}, err 121 } 122 // Deployment created successfully - return and requeue 123 return reconcile.Result{Requeue: true}, nil 124 } else if err != nil { 125 reqLogger.Error(err, "Failed to get Deployment") 126 return reconcile.Result{}, err 127 } 128 129 // Ensure the deployment size is the same as the spec 130 size := memcached.Spec.Size 131 if *found.Spec.Replicas != size { 132 found.Spec.Replicas = &size 133 err = r.client.Update(context.TODO(), found) 134 if err != nil { 135 reqLogger.Error(err, "Failed to update Deployment", "Deployment.Namespace", found.Namespace, "Deployment.Name", found.Name) 136 return reconcile.Result{}, err 137 } 138 // Spec updated - return and requeue 139 return reconcile.Result{Requeue: true}, nil 140 } 141 142 // Update the Memcached status with the pod names 143 // List the pods for this memcached's deployment 144 podList := &corev1.PodList{} 145 labelSelector := labels.SelectorFromSet(labelsForMemcached(memcached.Name)) 146 listOps := &client.ListOptions{Namespace: memcached.Namespace, LabelSelector: labelSelector} 147 err = r.client.List(context.TODO(), listOps, podList) 148 if err != nil { 149 reqLogger.Error(err, "Failed to list pods", "Memcached.Namespace", memcached.Namespace, "Memcached.Name", memcached.Name) 150 return reconcile.Result{}, err 151 } 152 podNames := getPodNames(podList.Items) 153 154 // Update status.Nodes if needed 155 if !reflect.DeepEqual(podNames, memcached.Status.Nodes) { 156 memcached.Status.Nodes = podNames 157 err := r.client.Status().Update(context.TODO(), memcached) 158 if err != nil { 159 reqLogger.Error(err, "Failed to update Memcached status") 160 return reconcile.Result{}, err 161 } 162 } 163 164 return reconcile.Result{}, nil 165 } 166 167 // deploymentForMemcached returns a memcached Deployment object 168 func (r *ReconcileMemcached) deploymentForMemcached(m *cachev1alpha1.Memcached) *appsv1.Deployment { 169 ls := labelsForMemcached(m.Name) 170 replicas := m.Spec.Size 171 172 dep := &appsv1.Deployment{ 173 TypeMeta: metav1.TypeMeta{ 174 APIVersion: "apps/v1", 175 Kind: "Deployment", 176 }, 177 ObjectMeta: metav1.ObjectMeta{ 178 Name: m.Name, 179 Namespace: m.Namespace, 180 }, 181 Spec: appsv1.DeploymentSpec{ 182 Replicas: &replicas, 183 Selector: &metav1.LabelSelector{ 184 MatchLabels: ls, 185 }, 186 Template: corev1.PodTemplateSpec{ 187 ObjectMeta: metav1.ObjectMeta{ 188 Labels: ls, 189 }, 190 Spec: corev1.PodSpec{ 191 Containers: []corev1.Container{{ 192 Image: "memcached:1.4.36-alpine", 193 Name: "memcached", 194 Command: []string{"memcached", "-m=64", "-o", "modern", "-v"}, 195 Ports: []corev1.ContainerPort{{ 196 ContainerPort: 11211, 197 Name: "memcached", 198 }}, 199 }}, 200 }, 201 }, 202 }, 203 } 204 // Set Memcached instance as the owner and controller 205 controllerutil.SetControllerReference(m, dep, r.scheme) 206 return dep 207 } 208 209 // labelsForMemcached returns the labels for selecting the resources 210 // belonging to the given memcached CR name. 211 func labelsForMemcached(name string) map[string]string { 212 return map[string]string{"app": "memcached", "memcached_cr": name} 213 } 214 215 // getPodNames returns the pod names of the array of pods passed in 216 func getPodNames(pods []corev1.Pod) []string { 217 var podNames []string 218 for _, pod := range pods { 219 podNames = append(podNames, pod.Name) 220 } 221 return podNames 222 }