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 }