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  }