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