github.com/inspektor-gadget/inspektor-gadget@v0.28.1/pkg/controllers/trace_controller.go (about)

     1  // Copyright 2021 The Inspektor Gadget 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 controllers
    16  
    17  import (
    18  	"context"
    19  	"errors"
    20  	"fmt"
    21  	"os"
    22  	"strings"
    23  
    24  	log "github.com/sirupsen/logrus"
    25  	apiequality "k8s.io/apimachinery/pkg/api/equality"
    26  	k8serrors "k8s.io/apimachinery/pkg/api/errors"
    27  	"k8s.io/apimachinery/pkg/runtime"
    28  	ctrl "sigs.k8s.io/controller-runtime"
    29  	"sigs.k8s.io/controller-runtime/pkg/client"
    30  	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
    31  
    32  	gadgetv1alpha1 "github.com/inspektor-gadget/inspektor-gadget/pkg/apis/gadget/v1alpha1"
    33  	"github.com/inspektor-gadget/inspektor-gadget/pkg/gadget-collection/gadgets"
    34  	"github.com/inspektor-gadget/inspektor-gadget/pkg/gadgettracermanager"
    35  )
    36  
    37  const (
    38  	/* Inspired from Gardener
    39  	 * https://gardener.cloud/docs/guides/administer_shoots/trigger-shoot-operations/
    40  	 */
    41  
    42  	GadgetOperation = "gadget.kinvolk.io/operation"
    43  	GadgetFinalizer = "gadget.kinvolk.io/finalizer"
    44  )
    45  
    46  // TraceReconciler reconciles a Trace object
    47  type TraceReconciler struct {
    48  	Client client.Client
    49  	Scheme *runtime.Scheme
    50  	Node   string
    51  
    52  	// TraceFactories contains the trace factories keyed by the gadget name
    53  	TraceFactories map[string]gadgets.TraceFactory
    54  	TracerManager  *gadgettracermanager.GadgetTracerManager
    55  }
    56  
    57  func updateTraceStatus(ctx context.Context, cli client.Client,
    58  	traceNsName string,
    59  	trace *gadgetv1alpha1.Trace,
    60  	patch client.Patch,
    61  ) {
    62  	log.Infof("Updating new status of trace %q: "+
    63  		"state=%s operationError=%q operationWarning=%q output=<%d characters>",
    64  		traceNsName,
    65  		trace.Status.State,
    66  		trace.Status.OperationError,
    67  		trace.Status.OperationWarning,
    68  		len(trace.Status.Output),
    69  	)
    70  
    71  	err := cli.Status().Patch(ctx, trace, patch)
    72  	if err != nil {
    73  		log.Errorf("Failed to update trace %q status: %s", traceNsName, err)
    74  	}
    75  }
    76  
    77  func setTraceOpError(ctx context.Context, cli client.Client,
    78  	traceNsName string,
    79  	trace *gadgetv1alpha1.Trace,
    80  	strError string,
    81  ) {
    82  	patch := client.MergeFrom(trace.DeepCopy())
    83  	trace.Status.OperationError = strError
    84  	updateTraceStatus(ctx, cli, traceNsName, trace, patch)
    85  }
    86  
    87  //+kubebuilder:rbac:groups=gadget.kinvolk.io,resources=traces,verbs=get;list;watch;create;update;patch;delete
    88  //+kubebuilder:rbac:groups=gadget.kinvolk.io,resources=traces/status,verbs=get;update;patch
    89  //+kubebuilder:rbac:groups=gadget.kinvolk.io,resources=traces/finalizers,verbs=update
    90  
    91  // Reconcile is part of the main kubernetes reconciliation loop which aims to
    92  // move the current state of the cluster closer to the desired state.
    93  // TODO(user): Modify the Reconcile function to compare the state specified by
    94  // the Trace object against the actual cluster state, and then
    95  // perform operations to make the cluster state reflect the state specified by
    96  // the user.
    97  //
    98  // For more details, check Reconcile and its Result here:
    99  // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.8.3/pkg/reconcile
   100  func (r *TraceReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
   101  	trace := &gadgetv1alpha1.Trace{}
   102  	err := r.Client.Get(ctx, req.NamespacedName, trace)
   103  	if err != nil {
   104  		if k8serrors.IsNotFound(err) {
   105  			log.Infof("Trace %q has been deleted", req.NamespacedName.String())
   106  			return ctrl.Result{}, nil
   107  		}
   108  		log.Errorf("Failed to get Trace %q: %s", req.NamespacedName.String(), err)
   109  		return ctrl.Result{}, err
   110  	}
   111  
   112  	// Each node handles their own traces
   113  	if trace.Spec.Node != r.Node {
   114  		return ctrl.Result{}, nil
   115  	}
   116  
   117  	log.Infof("Reconcile trace %s (gadget %s, node %s)",
   118  		req.NamespacedName,
   119  		trace.Spec.Gadget,
   120  		trace.Spec.Node)
   121  
   122  	// Verify if the Trace is under deletion. Notice we must do it before
   123  	// checking the Trace specs to avoid blocking the deletion.
   124  	if !trace.ObjectMeta.DeletionTimestamp.IsZero() {
   125  		if controllerutil.ContainsFinalizer(trace, GadgetFinalizer) {
   126  			// Inform the factory (if valid gadget) that the trace is being deleted
   127  			factory, ok := r.TraceFactories[trace.Spec.Gadget]
   128  			if ok {
   129  				factory.Delete(req.NamespacedName.String())
   130  			}
   131  
   132  			if r.TracerManager != nil {
   133  				err = r.TracerManager.RemoveTracer(
   134  					gadgets.TraceNameFromNamespacedName(req.NamespacedName),
   135  				)
   136  				if err != nil {
   137  					// Print error message but don't try again later
   138  					log.Errorf("Failed to delete tracer BPF map: %s", err)
   139  				}
   140  			}
   141  
   142  			// Remove our finalizer
   143  			controllerutil.RemoveFinalizer(trace, GadgetFinalizer)
   144  			if err := r.Client.Update(ctx, trace); err != nil {
   145  				log.Errorf("Failed to remove finalizer: %s", err)
   146  				return ctrl.Result{}, err
   147  			}
   148  		}
   149  		// Stop reconciliation as the Trace is being deleted
   150  		log.Infof("Let trace %s be deleted", req.NamespacedName)
   151  		return ctrl.Result{}, nil
   152  	}
   153  
   154  	// Check trace specs before adding the finalizer and registering the trace.
   155  	// If there is an error updating the Trace, return anyway nil to prevent
   156  	// the Reconcile() from being called again and again by the controller.
   157  	factory, ok := r.TraceFactories[trace.Spec.Gadget]
   158  	if !ok {
   159  		setTraceOpError(ctx, r.Client, req.NamespacedName.String(),
   160  			trace, fmt.Sprintf("Unknown gadget %q", trace.Spec.Gadget))
   161  
   162  		return ctrl.Result{}, nil
   163  	}
   164  	if trace.Spec.RunMode != gadgetv1alpha1.RunModeManual {
   165  		setTraceOpError(ctx, r.Client, req.NamespacedName.String(),
   166  			trace, fmt.Sprintf("Unsupported RunMode %q for gadget %q",
   167  				trace.Spec.RunMode, trace.Spec.Gadget))
   168  
   169  		return ctrl.Result{}, nil
   170  	}
   171  	outputModes := factory.OutputModesSupported()
   172  	if _, ok := outputModes[trace.Spec.OutputMode]; !ok {
   173  		setTraceOpError(ctx, r.Client, req.NamespacedName.String(),
   174  			trace, fmt.Sprintf("Unsupported OutputMode %q for gadget %q",
   175  				trace.Spec.OutputMode, trace.Spec.Gadget))
   176  
   177  		return ctrl.Result{}, nil
   178  	}
   179  
   180  	// The Trace is not being deleted and specs are valid, we can register our finalizer
   181  	beforeFinalizer := trace.DeepCopy()
   182  	controllerutil.AddFinalizer(trace, GadgetFinalizer)
   183  	if err := r.Client.Patch(ctx, trace, client.MergeFrom(beforeFinalizer)); err != nil {
   184  		log.Errorf("Failed to add finalizer: %s", err)
   185  		return ctrl.Result{}, err
   186  	}
   187  
   188  	// Register tracer
   189  	if r.TracerManager != nil {
   190  		err = r.TracerManager.AddTracer(
   191  			gadgets.TraceNameFromNamespacedName(req.NamespacedName),
   192  			*gadgets.ContainerSelectorFromContainerFilter(trace.Spec.Filter),
   193  		)
   194  		if err != nil && !errors.Is(err, os.ErrExist) {
   195  			log.Errorf("Failed to add tracer BPF map: %s", err)
   196  			return ctrl.Result{}, err
   197  		}
   198  	}
   199  
   200  	// Lookup annotations
   201  	if trace.ObjectMeta.Annotations == nil {
   202  		log.Info("No annotations. Nothing to do.")
   203  		return ctrl.Result{}, nil
   204  	}
   205  
   206  	// For now, only support control via the GADGET_OPERATION
   207  	var op string
   208  	if op, ok = trace.ObjectMeta.Annotations[GadgetOperation]; !ok {
   209  		log.Info("No operation annotation. Nothing to do.")
   210  		return ctrl.Result{}, nil
   211  	}
   212  
   213  	params := make(map[string]string)
   214  	for k, v := range trace.ObjectMeta.Annotations {
   215  		if !strings.HasPrefix(k, GadgetOperation+"-") {
   216  			continue
   217  		}
   218  		params[strings.TrimPrefix(k, GadgetOperation+"-")] = v
   219  	}
   220  
   221  	log.Infof("Gadget %s operation %q on %s", trace.Spec.Gadget, op, req.NamespacedName)
   222  
   223  	// Remove annotations first to avoid another execution in the next
   224  	// reconciliation loop.
   225  	withAnnotation := trace.DeepCopy()
   226  	annotations := trace.GetAnnotations()
   227  	delete(annotations, GadgetOperation)
   228  	for k := range params {
   229  		delete(annotations, GadgetOperation+"-"+k)
   230  	}
   231  	trace.SetAnnotations(annotations)
   232  	err = r.Client.Patch(ctx, trace, client.MergeFrom(withAnnotation))
   233  	if err != nil {
   234  		log.Errorf("Failed to update trace: %s", err)
   235  		return ctrl.Result{}, err
   236  	}
   237  
   238  	// Check operation is supported for this specific gadget
   239  	gadgetOperation, ok := factory.Operations()[gadgetv1alpha1.Operation(op)]
   240  	if !ok {
   241  		setTraceOpError(ctx, r.Client, req.NamespacedName.String(),
   242  			trace, fmt.Sprintf("Unsupported operation %q for gadget %q",
   243  				op, trace.Spec.Gadget))
   244  
   245  		return ctrl.Result{}, nil
   246  	}
   247  
   248  	// Call gadget operation
   249  	traceBeforeOperation := trace.DeepCopy()
   250  	trace.Status.OperationError = ""
   251  	trace.Status.OperationWarning = ""
   252  	patch := client.MergeFrom(traceBeforeOperation)
   253  	gadgetOperation.Operation(req.NamespacedName.String(), trace)
   254  
   255  	if apiequality.Semantic.DeepEqual(traceBeforeOperation.Status, trace.Status) {
   256  		log.Info("Gadget completed operation without changing the trace status")
   257  	} else {
   258  		log.Infof("Gadget completed operation. Trace status will be updated accordingly")
   259  		updateTraceStatus(ctx, r.Client, req.NamespacedName.String(), trace, patch)
   260  	}
   261  
   262  	return ctrl.Result{}, nil
   263  }
   264  
   265  // SetupWithManager sets up the controller with the Manager.
   266  func (r *TraceReconciler) SetupWithManager(mgr ctrl.Manager) error {
   267  	return ctrl.NewControllerManagedBy(mgr).
   268  		For(&gadgetv1alpha1.Trace{}).
   269  		Complete(r)
   270  }