1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package recorder
17
18 import (
19 "context"
20 "fmt"
21 "reflect"
22 "time"
23
24 "github.com/go-logr/logr"
25 "github.com/iancoleman/strcase"
26 v1 "k8s.io/api/core/v1"
27 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
28 "k8s.io/apimachinery/pkg/runtime"
29 "k8s.io/apimachinery/pkg/types"
30 "k8s.io/client-go/tools/record/util"
31 ref "k8s.io/client-go/tools/reference"
32 "k8s.io/klog/v2"
33 "sigs.k8s.io/controller-runtime/pkg/client"
34 )
35
36 type ChaosRecorder interface {
37 Event(object runtime.Object, ev ChaosEvent)
38 }
39
40 type chaosRecorder struct {
41 log logr.Logger
42 source v1.EventSource
43 client client.Client
44 scheme *runtime.Scheme
45 }
46
47 func (r *chaosRecorder) Event(object runtime.Object, ev ChaosEvent) {
48 eventtype := ev.Type()
49 reason := ev.Reason()
50 message := ev.Message()
51
52 annotations, err := generateAnnotations(ev)
53 if err != nil {
54 r.log.Error(err, "failed to generate annotations for event", "event", ev)
55 }
56
57 ref, err := ref.GetReference(r.scheme, object)
58 if err != nil {
59 r.log.Error(err, "fail to construct reference", "object", object)
60 return
61 }
62
63 if !util.ValidateEventType(eventtype) {
64 klog.Errorf("Unsupported event type: '%v'", eventtype)
65 return
66 }
67
68 event := r.makeEvent(ref, annotations, eventtype, reason, message)
69 event.Source = r.source
70 go func() {
71 err := r.client.Create(context.TODO(), event)
72 if err != nil {
73 r.log.Error(err, "fail to submit event", "event", event)
74 }
75 }()
76 }
77
78 func (r *chaosRecorder) makeEvent(ref *v1.ObjectReference, annotations map[string]string, eventtype, reason, message string) *v1.Event {
79 t := metav1.Time{Time: time.Now()}
80 namespace := ref.Namespace
81 if namespace == "" {
82 namespace = metav1.NamespaceDefault
83 }
84 return &v1.Event{
85 ObjectMeta: metav1.ObjectMeta{
86 Name: fmt.Sprintf("%v.%x", ref.Name, t.UnixNano()),
87 Namespace: namespace,
88 Annotations: annotations,
89 },
90 InvolvedObject: *ref,
91 Reason: reason,
92 Message: message,
93 FirstTimestamp: t,
94 LastTimestamp: t,
95 Count: 1,
96 Type: eventtype,
97 }
98 }
99
100 type ChaosEvent interface {
101 Type() string
102 Reason() string
103 Message() string
104 }
105
106 var allEvents = make(map[string]ChaosEvent)
107
108 func register(ev ...ChaosEvent) {
109 for _, ev := range ev {
110 val := reflect.ValueOf(ev)
111 val = reflect.Indirect(val)
112
113 allEvents[strcase.ToKebab(val.Type().Name())] = ev
114 }
115 }
116
117 type RecorderBuilder struct {
118 c client.Client
119 logger logr.Logger
120 scheme *runtime.Scheme
121 }
122
123 func (b *RecorderBuilder) Build(name string) ChaosRecorder {
124 return &chaosRecorder{
125 log: b.logger.WithName("event-recorder-" + name),
126 source: v1.EventSource{
127 Component: name,
128 },
129 client: b.c,
130 scheme: b.scheme,
131 }
132 }
133
134 func NewRecorderBuilder(c client.Client, logger logr.Logger, scheme *runtime.Scheme) *RecorderBuilder {
135 return &RecorderBuilder{
136 c,
137 logger,
138 scheme,
139 }
140 }
141
142 type debugRecorder struct {
143 Events map[types.NamespacedName][]ChaosEvent
144 }
145
146 func (d *debugRecorder) Event(object runtime.Object, ev ChaosEvent) {
147 obj := object.(metav1.Object)
148 id := types.NamespacedName{
149 Namespace: obj.GetNamespace(),
150 Name: obj.GetName(),
151 }
152
153 if d.Events[id] == nil {
154 d.Events[id] = []ChaosEvent{}
155 }
156
157 d.Events[id] = append(d.Events[id], ev)
158 }
159
160 func NewDebugRecorder() *debugRecorder {
161 return &debugRecorder{
162 Events: make(map[types.NamespacedName][]ChaosEvent),
163 }
164 }
165