inspektor-gadget

Форк
0
/
trace_controller.go 
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

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
}
271

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.