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