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