cloudfoundry-incubator/eirini

View on GitHub
k8s/informers/event/crash.go

Summary

Maintainability
A
40 mins
Test Coverage
A
96%
package event

import (
    "context"
    "strconv"

    "code.cloudfoundry.org/eirini/events"
    "code.cloudfoundry.org/eirini/k8s/stset"
    "code.cloudfoundry.org/lager"
    "github.com/pkg/errors"
    corev1 "k8s.io/api/core/v1"
    apierrors "k8s.io/apimachinery/pkg/api/errors"
    "sigs.k8s.io/controller-runtime/pkg/client"
    "sigs.k8s.io/controller-runtime/pkg/reconcile"
)

const (
    CrashLoopBackOff           = "CrashLoopBackOff"
    CreateContainerConfigError = "CreateContainerConfigError"
)

//counterfeiter:generate . CrashEventGenerator
//counterfeiter:generate . CrashEmitter
//counterfeiter:generate -o eventfakes/fake_controller_runtime_client.go sigs.k8s.io/controller-runtime/pkg/client.Client

type CrashEventGenerator interface {
    Generate(context.Context, *corev1.Pod, lager.Logger) (events.CrashEvent, bool)
}

type CrashEmitter interface {
    Emit(events.CrashEvent) error
}

type CrashReconciler struct {
    logger         lager.Logger
    client         client.Client
    eventGenerator CrashEventGenerator
    crashEmitter   CrashEmitter
}

func NewCrashReconciler(
    logger lager.Logger,
    client client.Client,
    eventGenerator CrashEventGenerator,
    crashEmitter CrashEmitter,
) *CrashReconciler {
    return &CrashReconciler{
        logger:         logger,
        client:         client,
        eventGenerator: eventGenerator,
        crashEmitter:   crashEmitter,
    }
}

func (c *CrashReconciler) Reconcile(ctx context.Context, request reconcile.Request) (reconcile.Result, error) {
    logger := c.logger.Session("reconcile-pod-crash",
        lager.Data{
            "name":      request.NamespacedName.Name,
            "namespace": request.NamespacedName.Namespace,
        })

    pod := &corev1.Pod{}

    err := c.client.Get(ctx, request.NamespacedName, pod)
    if err != nil {
        if apierrors.IsNotFound(err) {
            logger.Info("pod-not-found", lager.Data{"error": err})

            return reconcile.Result{}, nil
        }

        logger.Error("failed-to-get-pod", err)

        return reconcile.Result{}, errors.Wrap(err, "failed to get pod")
    }

    event, send := c.eventGenerator.Generate(ctx, pod, c.logger)
    if !send {
        logger.Debug("not-sending-event")

        return reconcile.Result{}, nil
    }

    if strconv.FormatInt(event.CrashTimestamp, 10) == pod.Annotations[stset.AnnotationLastReportedAppCrash] { // nolint:gomnd
        logger.Debug("event-already-sent")

        return reconcile.Result{}, nil
    }

    logger.Info("generated-event", lager.Data{"event": event})

    err = c.crashEmitter.Emit(event)
    if err != nil {
        logger.Error("failed-to-emit-event", err)

        return reconcile.Result{}, errors.Wrap(err, "failed to emit event")
    }

    logger.Info("emitted-event")

    newPod := pod.DeepCopy()
    if newPod.Annotations == nil {
        newPod.Annotations = map[string]string{}
    }

    newPod.Annotations[stset.AnnotationLastReportedAppCrash] = strconv.FormatInt(event.CrashTimestamp, 10) // nolint:gomnd

    if err = c.client.Patch(ctx, newPod, client.MergeFrom(pod)); err != nil {
        logger.Error("failed-to-set-last-crash-time-on-pod", err)
    }

    return reconcile.Result{}, nil
}