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