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  }