Last active
December 14, 2018 05:06
-
-
Save hasbro17/f5e60cecc0b2f7e36f73a06c2205ab51 to your computer and use it in GitHub Desktop.
Podset Controller
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package podset | |
import ( | |
"context" | |
"reflect" | |
appv1alpha1 "github.com/redhat/podset-operator/pkg/apis/app/v1alpha1" | |
corev1 "k8s.io/api/core/v1" | |
"k8s.io/apimachinery/pkg/api/errors" | |
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | |
"k8s.io/apimachinery/pkg/labels" | |
"k8s.io/apimachinery/pkg/runtime" | |
"sigs.k8s.io/controller-runtime/pkg/client" | |
"sigs.k8s.io/controller-runtime/pkg/controller" | |
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" | |
"sigs.k8s.io/controller-runtime/pkg/handler" | |
"sigs.k8s.io/controller-runtime/pkg/manager" | |
"sigs.k8s.io/controller-runtime/pkg/reconcile" | |
logf "sigs.k8s.io/controller-runtime/pkg/runtime/log" | |
"sigs.k8s.io/controller-runtime/pkg/source" | |
) | |
var log = logf.Log.WithName("controller_podset") | |
/** | |
* USER ACTION REQUIRED: This is a scaffold file intended for the user to modify with their own Controller | |
* business logic. Delete these comments after modifying this file.* | |
*/ | |
// Add creates a new PodSet Controller and adds it to the Manager. The Manager will set fields on the Controller | |
// and Start it when the Manager is Started. | |
func Add(mgr manager.Manager) error { | |
return add(mgr, newReconciler(mgr)) | |
} | |
// newReconciler returns a new reconcile.Reconciler | |
func newReconciler(mgr manager.Manager) reconcile.Reconciler { | |
return &ReconcilePodSet{client: mgr.GetClient(), scheme: mgr.GetScheme()} | |
} | |
// add adds a new Controller to mgr with r as the reconcile.Reconciler | |
func add(mgr manager.Manager, r reconcile.Reconciler) error { | |
// Create a new controller | |
c, err := controller.New("podset-controller", mgr, controller.Options{Reconciler: r}) | |
if err != nil { | |
return err | |
} | |
// Watch for changes to primary resource PodSet | |
err = c.Watch(&source.Kind{Type: &appv1alpha1.PodSet{}}, &handler.EnqueueRequestForObject{}) | |
if err != nil { | |
return err | |
} | |
// TODO(user): Modify this to be the types you create that are owned by the primary resource | |
// Watch for changes to secondary resource Pods and requeue the owner PodSet | |
err = c.Watch(&source.Kind{Type: &corev1.Pod{}}, &handler.EnqueueRequestForOwner{ | |
IsController: true, | |
OwnerType: &appv1alpha1.PodSet{}, | |
}) | |
if err != nil { | |
return err | |
} | |
return nil | |
} | |
var _ reconcile.Reconciler = &ReconcilePodSet{} | |
// ReconcilePodSet reconciles a PodSet object | |
type ReconcilePodSet struct { | |
// This client, initialized using mgr.Client() above, is a split client | |
// that reads objects from the cache and writes to the apiserver | |
client client.Client | |
scheme *runtime.Scheme | |
} | |
// Reconcile reads that state of the cluster for a PodSet object and makes changes based on the state read | |
// and what is in the PodSet.Spec | |
// TODO(user): Modify this Reconcile function to implement your Controller logic. This example creates | |
// a Pod as an example | |
// Note: | |
// The Controller will requeue the Request to be processed again if the returned error is non-nil or | |
// Result.Requeue is true, otherwise upon completion it will remove the work from the queue. | |
func (r *ReconcilePodSet) Reconcile(request reconcile.Request) (reconcile.Result, error) { | |
reqLogger := log.WithValues("Request.Namespace", request.Namespace, "Request.Name", request.Name) | |
reqLogger.Info("Reconciling PodSet") | |
// Fetch the PodSet instance | |
podSet := &appv1alpha1.PodSet{} | |
err := r.client.Get(context.TODO(), request.NamespacedName, podSet) | |
if err != nil { | |
if errors.IsNotFound(err) { | |
// Request object not found, could have been deleted after reconcile request. | |
// Owned objects are automatically garbage collected. For additional cleanup logic use finalizers. | |
// Return and don't requeue | |
return reconcile.Result{}, nil | |
} | |
// Error reading the object - requeue the request. | |
return reconcile.Result{}, err | |
} | |
// List all pods owned by this PodSet instance | |
podList := &corev1.PodList{} | |
lbs := map[string]string{ | |
"app": podSet.Name, | |
"version": "v0.1", | |
} | |
labelSelector := labels.SelectorFromSet(lbs) | |
listOps := &client.ListOptions{Namespace: podSet.Namespace, LabelSelector: labelSelector} | |
if err = r.client.List(context.TODO(), listOps, podList); err != nil { | |
return reconcile.Result{}, err | |
} | |
// Count the pods that are pending or running as available | |
var available []corev1.Pod | |
for _, pod := range podList.Items { | |
if pod.ObjectMeta.DeletionTimestamp != nil { | |
continue | |
} | |
if pod.Status.Phase == corev1.PodRunning || pod.Status.Phase == corev1.PodPending { | |
available = append(available, pod) | |
} | |
} | |
numAvailable := int32(len(available)) | |
availableNames := []string{} | |
for _, pod := range available { | |
availableNames = append(availableNames, pod.ObjectMeta.Name) | |
} | |
// Update the status if necessary | |
status := appv1alpha1.PodSetStatus{ | |
PodNames: availableNames, | |
} | |
if !reflect.DeepEqual(podSet.Status, status) { | |
podSet.Status = status | |
err = r.client.Update(context.TODO(), podSet) | |
if err != nil { | |
reqLogger.Error(err, "Failed to update PodSet status") | |
return reconcile.Result{}, err | |
} | |
} | |
if numAvailable > podSet.Spec.Replicas { | |
reqLogger.Info("Scaling down pods", "Currently available", numAvailable, "Required replicas", podSet.Spec.Replicas) | |
diff := numAvailable - podSet.Spec.Replicas | |
dpods := available[:diff] | |
for _, dpod := range dpods { | |
err = r.client.Delete(context.TODO(), &dpod) | |
if err != nil { | |
reqLogger.Error(err, "Failed to delete pod", "pod.name", dpod.Name) | |
return reconcile.Result{}, err | |
} | |
} | |
return reconcile.Result{Requeue: true}, nil | |
} | |
if numAvailable < podSet.Spec.Replicas { | |
reqLogger.Info("Scaling up pods", "Currently available", numAvailable, "Required replicas", podSet.Spec.Replicas) | |
// Define a new Pod object | |
pod := newPodForCR(podSet) | |
// Set PodSet instance as the owner and controller | |
if err := controllerutil.SetControllerReference(podSet, pod, r.scheme); err != nil { | |
return reconcile.Result{}, err | |
} | |
err = r.client.Create(context.TODO(), pod) | |
if err != nil { | |
reqLogger.Error(err, "Failed to delete pod", "pod.name", pod.Name) | |
return reconcile.Result{}, err | |
} | |
return reconcile.Result{Requeue: true}, nil | |
} | |
return reconcile.Result{}, nil | |
} | |
// newPodForCR returns a busybox pod with the same name/namespace as the cr | |
func newPodForCR(cr *appv1alpha1.PodSet) *corev1.Pod { | |
labels := map[string]string{ | |
"app": cr.Name, | |
"version": "v0.1", | |
} | |
return &corev1.Pod{ | |
ObjectMeta: metav1.ObjectMeta{ | |
GenerateName: cr.Name + "-pod", | |
Namespace: cr.Namespace, | |
Labels: labels, | |
}, | |
Spec: corev1.PodSpec{ | |
Containers: []corev1.Container{ | |
{ | |
Name: "busybox", | |
Image: "busybox", | |
Command: []string{"sleep", "3600"}, | |
}, | |
}, | |
}, | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment