github.com/mkimuram/operator-sdk@v0.7.1-0.20190410172100-52ad33a4bda0/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 var _ reconcile.Reconciler = &ReconcileMemcached{} 72 73 // ReconcileMemcached reconciles a Memcached object 74 type ReconcileMemcached struct { 75 // TODO: Clarify the split client 76 // This client, initialized using mgr.Client() above, is a split client 77 // that reads objects from the cache and writes to the apiserver 78 client client.Client 79 scheme *runtime.Scheme 80 } 81 82 // Reconcile reads that state of the cluster for a Memcached object and makes changes based on the state read 83 // and what is in the Memcached.Spec 84 // TODO(user): Modify this Reconcile function to implement your Controller logic. This example creates 85 // a Memcached Deployment for each Memcached CR 86 // Note: 87 // The Controller will requeue the Request to be processed again if the returned error is non-nil or 88 // Result.Requeue is true, otherwise upon completion it will remove the work from the queue. 89 func (r *ReconcileMemcached) Reconcile(request reconcile.Request) (reconcile.Result, error) { 90 reqLogger := log.WithValues("Request.Namespace", request.Namespace, "Request.Name", request.Name) 91 reqLogger.Info("Reconciling Memcached") 92 93 // Fetch the Memcached instance 94 memcached := &cachev1alpha1.Memcached{} 95 err := r.client.Get(context.TODO(), request.NamespacedName, memcached) 96 if err != nil { 97 if errors.IsNotFound(err) { 98 // Request object not found, could have been deleted after reconcile request. 99 // Owned objects are automatically garbage collected. For additional cleanup logic use finalizers. 100 // Return and don't requeue 101 reqLogger.Info("Memcached resource not found. Ignoring since object must be deleted") 102 return reconcile.Result{}, nil 103 } 104 // Error reading the object - requeue the request. 105 reqLogger.Error(err, "Failed to get Memcached") 106 return reconcile.Result{}, err 107 } 108 109 // Check if the deployment already exists, if not create a new one 110 found := &appsv1.Deployment{} 111 err = r.client.Get(context.TODO(), types.NamespacedName{Name: memcached.Name, Namespace: memcached.Namespace}, found) 112 if err != nil && errors.IsNotFound(err) { 113 // Define a new deployment 114 dep := r.deploymentForMemcached(memcached) 115 reqLogger.Info("Creating a new Deployment", "Deployment.Namespace", dep.Namespace, "Deployment.Name", dep.Name) 116 err = r.client.Create(context.TODO(), dep) 117 if err != nil { 118 reqLogger.Error(err, "Failed to create new Deployment", "Deployment.Namespace", dep.Namespace, "Deployment.Name", dep.Name) 119 return reconcile.Result{}, err 120 } 121 // Deployment created successfully - return and requeue 122 return reconcile.Result{Requeue: true}, nil 123 } else if err != nil { 124 reqLogger.Error(err, "Failed to get Deployment") 125 return reconcile.Result{}, err 126 } 127 128 // Ensure the deployment size is the same as the spec 129 size := memcached.Spec.Size 130 if *found.Spec.Replicas != size { 131 found.Spec.Replicas = &size 132 err = r.client.Update(context.TODO(), found) 133 if err != nil { 134 reqLogger.Error(err, "Failed to update Deployment", "Deployment.Namespace", found.Namespace, "Deployment.Name", found.Name) 135 return reconcile.Result{}, err 136 } 137 // Spec updated - return and requeue 138 return reconcile.Result{Requeue: true}, nil 139 } 140 141 // Update the Memcached status with the pod names 142 // List the pods for this memcached's deployment 143 podList := &corev1.PodList{} 144 labelSelector := labels.SelectorFromSet(labelsForMemcached(memcached.Name)) 145 listOps := &client.ListOptions{Namespace: memcached.Namespace, LabelSelector: labelSelector} 146 err = r.client.List(context.TODO(), listOps, podList) 147 if err != nil { 148 reqLogger.Error(err, "Failed to list pods", "Memcached.Namespace", memcached.Namespace, "Memcached.Name", memcached.Name) 149 return reconcile.Result{}, err 150 } 151 podNames := getPodNames(podList.Items) 152 153 // Update status.Nodes if needed 154 if !reflect.DeepEqual(podNames, memcached.Status.Nodes) { 155 memcached.Status.Nodes = podNames 156 err := r.client.Status().Update(context.TODO(), memcached) 157 if err != nil { 158 reqLogger.Error(err, "Failed to update Memcached status") 159 return reconcile.Result{}, err 160 } 161 } 162 163 return reconcile.Result{}, nil 164 } 165 166 // deploymentForMemcached returns a memcached Deployment object 167 func (r *ReconcileMemcached) deploymentForMemcached(m *cachev1alpha1.Memcached) *appsv1.Deployment { 168 ls := labelsForMemcached(m.Name) 169 replicas := m.Spec.Size 170 171 dep := &appsv1.Deployment{ 172 TypeMeta: metav1.TypeMeta{ 173 APIVersion: "apps/v1", 174 Kind: "Deployment", 175 }, 176 ObjectMeta: metav1.ObjectMeta{ 177 Name: m.Name, 178 Namespace: m.Namespace, 179 }, 180 Spec: appsv1.DeploymentSpec{ 181 Replicas: &replicas, 182 Selector: &metav1.LabelSelector{ 183 MatchLabels: ls, 184 }, 185 Template: corev1.PodTemplateSpec{ 186 ObjectMeta: metav1.ObjectMeta{ 187 Labels: ls, 188 }, 189 Spec: corev1.PodSpec{ 190 Containers: []corev1.Container{{ 191 Image: "memcached:1.4.36-alpine", 192 Name: "memcached", 193 Command: []string{"memcached", "-m=64", "-o", "modern", "-v"}, 194 Ports: []corev1.ContainerPort{{ 195 ContainerPort: 11211, 196 Name: "memcached", 197 }}, 198 }}, 199 }, 200 }, 201 }, 202 } 203 // Set Memcached instance as the owner and controller 204 controllerutil.SetControllerReference(m, dep, r.scheme) 205 return dep 206 } 207 208 // labelsForMemcached returns the labels for selecting the resources 209 // belonging to the given memcached CR name. 210 func labelsForMemcached(name string) map[string]string { 211 return map[string]string{"app": "memcached", "memcached_cr": name} 212 } 213 214 // getPodNames returns the pod names of the array of pods passed in 215 func getPodNames(pods []corev1.Pod) []string { 216 var podNames []string 217 for _, pod := range pods { 218 podNames = append(podNames, pod.Name) 219 } 220 return podNames 221 }