Skip to content

Commit c1e3355

Browse files
JustinKuliopenshift-ci[bot]
authored andcommitted
Break ties when compliance timestamps collide
Uses the microsecond `EventTime` field on the event when present, or it tries to use a hexadecimal-encoded nanosecond timestamp which is in the name of events created by controller-runtime. Refs: - https://github.com/stolostron/backlog/issues/26039 Signed-off-by: Justin Kulikauskas <[email protected]>
1 parent b2d7bfa commit c1e3355

File tree

9 files changed

+445
-12
lines changed

9 files changed

+445
-12
lines changed

controllers/statussync/policy_status_sync.go

Lines changed: 57 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"os"
1010
"regexp"
1111
"sort"
12+
"strconv"
1213
"strings"
1314

1415
corev1 "k8s.io/api/core/v1"
@@ -171,7 +172,7 @@ func (r *PolicyReconciler) Reconcile(ctx context.Context, request reconcile.Requ
171172
return reconcile.Result{}, err
172173
}
173174
// filter events to current policy instance and build map
174-
eventForPolicyMap := make(map[string]*[]policiesv1.ComplianceHistory)
175+
eventForPolicyMap := make(map[string]*[]historyEvent)
175176
// panic if regexp invalid
176177
rgx := regexp.MustCompile(`(?i)^policy:\s*([A-Za-z0-9.-]+)\s*\/([A-Za-z0-9.-]+)`)
177178
for _, event := range eventList.Items {
@@ -180,14 +181,18 @@ func (r *PolicyReconciler) Reconcile(ctx context.Context, request reconcile.Requ
180181
if event.InvolvedObject.Kind == policiesv1.Kind && event.InvolvedObject.APIVersion == policiesv1APIVersion &&
181182
event.InvolvedObject.Name == instance.GetName() && reason != "" {
182183
templateName := rgx.FindStringSubmatch(event.Reason)[2]
183-
eventHistory := policiesv1.ComplianceHistory{
184-
LastTimestamp: event.LastTimestamp,
185-
Message: strings.TrimSpace(strings.TrimPrefix(event.Message, "(combined from similar events):")),
186-
EventName: event.GetName(),
184+
eventHistory := historyEvent{
185+
ComplianceHistory: policiesv1.ComplianceHistory{
186+
LastTimestamp: event.LastTimestamp,
187+
Message: strings.TrimSpace(strings.TrimPrefix(
188+
event.Message, "(combined from similar events):")),
189+
EventName: event.GetName(),
190+
},
191+
eventTime: *event.EventTime.DeepCopy(),
187192
}
188193

189194
if eventForPolicyMap[templateName] == nil {
190-
eventForPolicyMap[templateName] = &[]policiesv1.ComplianceHistory{}
195+
eventForPolicyMap[templateName] = &[]historyEvent{}
191196
}
192197

193198
templateEvents := append(*eventForPolicyMap[templateName], eventHistory)
@@ -236,7 +241,7 @@ func (r *PolicyReconciler) Reconcile(ctx context.Context, request reconcile.Requ
236241
}
237242
}
238243

239-
history := []policiesv1.ComplianceHistory{}
244+
history := []historyEvent{}
240245
if eventForPolicyMap[tName] != nil {
241246
history = *eventForPolicyMap[tName]
242247
}
@@ -252,20 +257,55 @@ func (r *PolicyReconciler) Reconcile(ctx context.Context, request reconcile.Requ
252257
break
253258
}
254259
}
255-
// doesn't exists, append to history
260+
// doesn't exist, append to history
256261
if !exists {
257-
history = append(history, ech)
262+
history = append(history, historyEvent{ComplianceHistory: ech})
258263
}
259264
}
260-
// sort by lasttimestamp
265+
// sort by lasttimestamp, break ties with EventTime (if present) or EventName
261266
sort.Slice(history, func(i, j int) bool {
262-
return history[i].LastTimestamp.Time.After(history[j].LastTimestamp.Time)
267+
if history[i].LastTimestamp.Equal(&history[j].LastTimestamp) {
268+
if !history[i].eventTime.IsZero() && !history[j].eventTime.IsZero() {
269+
reqLogger.V(2).Info("Event timestamp collision, order determined by EventTime",
270+
"event1Name", history[i].EventName, "event2Name", history[j].EventName)
271+
272+
return !history[i].eventTime.Before(&history[j].eventTime)
273+
}
274+
// Timestamps are the same: attempt to use the event name.
275+
// Conventionally (in client-go), the event name has a hexadecimal
276+
// nanosecond timestamp as a suffix after a period.
277+
iNameParts := strings.Split(history[i].EventName, ".")
278+
jNameParts := strings.Split(history[j].EventName, ".")
279+
errMsg := "Unable to interpret hexadecimal timestamp in event name, " +
280+
"can't guarantee ordering of events in this status"
281+
282+
iNanos, err := strconv.ParseInt(iNameParts[len(iNameParts)-1], 16, 64)
283+
if err != nil {
284+
reqLogger.Error(err, errMsg, "eventName", history[i].EventName)
285+
286+
return false
287+
}
288+
289+
jNanos, err := strconv.ParseInt(jNameParts[len(jNameParts)-1], 16, 64)
290+
if err != nil {
291+
reqLogger.Error(err, errMsg, "eventName", history[j].EventName)
292+
293+
return false
294+
}
295+
296+
reqLogger.V(2).Info("Event timestamp collision, order determined by hex timestamp in name",
297+
"event1Name", history[i].EventName, "event2Name", history[j].EventName)
298+
299+
return iNanos > jNanos
300+
}
301+
302+
return !history[i].LastTimestamp.Time.Before(history[j].LastTimestamp.Time)
263303
})
264304
// remove duplicates
265305
newHistory := []policiesv1.ComplianceHistory{}
266306

267307
for historyIndex := 0; historyIndex < len(history); historyIndex++ {
268-
newHistory = append(newHistory, history[historyIndex])
308+
newHistory = append(newHistory, history[historyIndex].ComplianceHistory)
269309

270310
for j := historyIndex; j < len(history); j++ {
271311
if history[historyIndex].EventName == history[j].EventName &&
@@ -365,3 +405,8 @@ func (r *PolicyReconciler) Reconcile(ctx context.Context, request reconcile.Requ
365405

366406
return reconcile.Result{}, nil
367407
}
408+
409+
type historyEvent struct {
410+
policiesv1.ComplianceHistory
411+
eventTime metav1.MicroTime
412+
}
Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
// Copyright (c) 2020 Red Hat, Inc.
2+
// Copyright Contributors to the Open Cluster Management project
3+
4+
package e2e
5+
6+
import (
7+
"context"
8+
"time"
9+
10+
. "github.com/onsi/ginkgo/v2"
11+
. "github.com/onsi/gomega"
12+
corev1 "k8s.io/api/core/v1"
13+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
14+
"open-cluster-management.io/governance-policy-propagator/test/utils"
15+
)
16+
17+
const (
18+
case11PolicyYaml string = "../resources/case11_ts_collision/case11-policy.yaml"
19+
case11PolicyName string = "default.case11-test-policy"
20+
case11Event1 string = "default.case11-test-policy.171a96193d32cf17"
21+
case11Event2 string = "default.case11-test-policy.171a96193dea32f4"
22+
case11Event3 string = "default.case11-test-policy.171a96193dea32f8"
23+
case11Event4 string = "default.case11-test-policy.four.171a96193d32cf17"
24+
case11Event5 string = "default.case11-test-policy.five.171a96193d32cf17"
25+
case11Event6 string = "default.case11-test-policy.six.171a96193d32cf17"
26+
case11hubconfig string = "--kubeconfig=../../kubeconfig_hub"
27+
case11managedconfig string = "--kubeconfig=../../kubeconfig_managed"
28+
)
29+
30+
func case11Event(name, namespace, message, evtype string, evtime time.Time, includeMS bool) error {
31+
event := &corev1.Event{
32+
ObjectMeta: metav1.ObjectMeta{
33+
Name: name,
34+
Namespace: namespace,
35+
CreationTimestamp: metav1.NewTime(evtime),
36+
},
37+
InvolvedObject: corev1.ObjectReference{
38+
Kind: "Policy",
39+
Namespace: namespace,
40+
Name: "default.case11-test-policy",
41+
// UID: "53719093-857c-4c9b-a1d2-187dfb8c6657",
42+
APIVersion: "policy.open-cluster-management.io/v1",
43+
},
44+
Reason: "policy: managed/case11-cfg-policy",
45+
Message: message,
46+
Source: corev1.EventSource{
47+
Component: "configuration-policy-controller",
48+
},
49+
FirstTimestamp: metav1.NewTime(evtime),
50+
LastTimestamp: metav1.NewTime(evtime),
51+
Count: 1,
52+
Type: evtype,
53+
}
54+
55+
if includeMS {
56+
event.EventTime = metav1.NewMicroTime(evtime)
57+
58+
// These fields must also be added to satisfy eventsv1.Event validation
59+
event.Action = "filler"
60+
event.ReportingController = "filler"
61+
event.ReportingInstance = "filler"
62+
}
63+
64+
_, err := clientManaged.CoreV1().Events(namespace).Create(
65+
context.TODO(),
66+
event,
67+
metav1.CreateOptions{},
68+
)
69+
70+
return err
71+
}
72+
73+
func case11cleanup() {
74+
out, err := utils.KubectlWithOutput(
75+
"delete", "-f", case11PolicyYaml, "-n", clusterNamespaceOnHub, case11hubconfig)
76+
if err != nil {
77+
Expect(out).Should(ContainSubstring("NotFound"))
78+
}
79+
80+
out, err = utils.KubectlWithOutput(
81+
"delete", "-f", case11PolicyYaml, "-n", clusterNamespace, case11managedconfig)
82+
if err != nil {
83+
Expect(out).Should(ContainSubstring("NotFound"))
84+
}
85+
86+
eventsToDelete := []string{
87+
case11Event1,
88+
case11Event2,
89+
case11Event3,
90+
case11Event4,
91+
case11Event5,
92+
case11Event6,
93+
}
94+
95+
for _, evname := range eventsToDelete {
96+
out, err = utils.KubectlWithOutput(
97+
"delete", "event", evname, "-n", clusterNamespace, case11managedconfig)
98+
if err != nil {
99+
Expect(out).Should(ContainSubstring("NotFound"))
100+
}
101+
}
102+
}
103+
104+
var _ = Describe("Test event sorting by name when timestamps collide", Ordered, func() {
105+
It("Creates the policy and one event, and shows compliant", func() {
106+
_, err := utils.KubectlWithOutput(
107+
"apply", "-f", case11PolicyYaml, "-n", clusterNamespaceOnHub, case11hubconfig,
108+
)
109+
Expect(err).Should(BeNil())
110+
111+
_, err = utils.KubectlWithOutput(
112+
"apply", "-f", case11PolicyYaml, "-n", clusterNamespace, case11managedconfig,
113+
)
114+
Expect(err).Should(BeNil())
115+
116+
Expect(case11Event(
117+
case11Event1,
118+
clusterNamespace,
119+
"Compliant; notification - this is the oldest event",
120+
"Normal",
121+
time.Date(2022, 10, 3, 14, 40, 47, 0, time.UTC),
122+
false,
123+
)).Should(BeNil())
124+
125+
Eventually(checkCompliance(case11PolicyName), defaultTimeoutSeconds, 1).
126+
Should(Equal("Compliant"))
127+
Consistently(checkCompliance(case11PolicyName), "15s", 1).
128+
Should(Equal("Compliant"))
129+
})
130+
131+
It("Creates a second event with the same timestamp, and shows noncompliant", func() {
132+
Expect(case11Event(
133+
case11Event2,
134+
clusterNamespace,
135+
"NonCompliant; violation - a problem sandwich",
136+
"Warning",
137+
time.Date(2022, 10, 3, 14, 40, 47, 0, time.UTC),
138+
false,
139+
)).Should(BeNil())
140+
141+
Eventually(checkCompliance(case11PolicyName), defaultTimeoutSeconds, 1).
142+
Should(Equal("NonCompliant"))
143+
Consistently(checkCompliance(case11PolicyName), "15s", 1).
144+
Should(Equal("NonCompliant"))
145+
})
146+
147+
It("Creates a third with the same timestamp, and shows compliant", func() {
148+
Expect(case11Event(
149+
case11Event3,
150+
clusterNamespace,
151+
"Compliant; notification - this should be the most recent",
152+
"Normal",
153+
time.Date(2022, 10, 3, 14, 40, 47, 0, time.UTC),
154+
false,
155+
)).Should(BeNil())
156+
157+
Eventually(checkCompliance(case11PolicyName), defaultTimeoutSeconds, 1).
158+
Should(Equal("Compliant"))
159+
Consistently(checkCompliance(case11PolicyName), "15s", 1).
160+
Should(Equal("Compliant"))
161+
})
162+
163+
AfterAll(case11cleanup)
164+
})
165+
166+
var _ = Describe("Test event sorting by eventtime when timestamps collide", Ordered, func() {
167+
It("Creates the policy and one event, and shows compliant", func() {
168+
_, err := utils.KubectlWithOutput(
169+
"apply", "-f", case11PolicyYaml, "-n", clusterNamespaceOnHub, case11hubconfig,
170+
)
171+
Expect(err).Should(BeNil())
172+
173+
_, err = utils.KubectlWithOutput(
174+
"apply", "-f", case11PolicyYaml, "-n", clusterNamespace, case11managedconfig,
175+
)
176+
Expect(err).Should(BeNil())
177+
178+
Expect(case11Event(
179+
case11Event4,
180+
clusterNamespace,
181+
"Compliant; notification - this is the oldest event",
182+
"Normal",
183+
time.Date(2022, 10, 3, 14, 40, 47, 111111, time.UTC),
184+
true,
185+
)).Should(BeNil())
186+
187+
Eventually(checkCompliance(case11PolicyName), defaultTimeoutSeconds, 1).
188+
Should(Equal("Compliant"))
189+
Consistently(checkCompliance(case11PolicyName), "15s", 1).
190+
Should(Equal("Compliant"))
191+
})
192+
193+
It("Creates a second event with the same timestamp, and shows noncompliant", func() {
194+
Expect(case11Event(
195+
case11Event5,
196+
clusterNamespace,
197+
"NonCompliant; violation - a problem sandwich",
198+
"Warning",
199+
time.Date(2022, 10, 3, 14, 40, 47, 222222, time.UTC),
200+
true,
201+
)).Should(BeNil())
202+
203+
Eventually(checkCompliance(case11PolicyName), defaultTimeoutSeconds, 1).
204+
Should(Equal("NonCompliant"))
205+
Consistently(checkCompliance(case11PolicyName), "15s", 1).
206+
Should(Equal("NonCompliant"))
207+
})
208+
209+
It("Creates a third with the same timestamp, and shows compliant", func() {
210+
Expect(case11Event(
211+
case11Event6,
212+
clusterNamespace,
213+
"Compliant; notification - this should be the most recent",
214+
"Warning",
215+
time.Date(2022, 10, 3, 14, 40, 47, 333333, time.UTC),
216+
true,
217+
)).Should(BeNil())
218+
219+
Eventually(checkCompliance(case11PolicyName), defaultTimeoutSeconds, 1).
220+
Should(Equal("Compliant"))
221+
Consistently(checkCompliance(case11PolicyName), "15s", 1).
222+
Should(Equal("Compliant"))
223+
})
224+
225+
AfterAll(case11cleanup)
226+
})
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
action: "filler"
2+
apiVersion: v1
3+
count: 1
4+
eventTime: "2022-10-03T14:40:47.222222Z"
5+
firstTimestamp: "2022-10-03T14:40:47Z"
6+
involvedObject:
7+
apiVersion: policy.open-cluster-management.io/v1
8+
kind: Policy
9+
name: default.case11-test-policy
10+
uid: 53719093-857c-4c9b-a1d2-187dfb8c6657
11+
kind: Event
12+
lastTimestamp: "2022-10-03T14:40:47Z"
13+
message: NonCompliant; violation - a problem sandwich
14+
metadata:
15+
creationTimestamp: "2022-10-03T14:40:47Z"
16+
name: default.case11-test-policy.b.171a96193d32cf17
17+
reason: 'policy: managed/case11-cfg-policy'
18+
reportingComponent: "filler"
19+
reportingInstance: "filler"
20+
source:
21+
component: configuration-policy-controller
22+
type: Warning
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
action: "filler"
2+
apiVersion: v1
3+
count: 1
4+
eventTime: "2022-10-03T14:40:47.111111Z"
5+
firstTimestamp: "2022-10-03T14:40:47Z"
6+
involvedObject:
7+
apiVersion: policy.open-cluster-management.io/v1
8+
kind: Policy
9+
name: default.case11-test-policy
10+
uid: 53719093-857c-4c9b-a1d2-187dfb8c6657
11+
kind: Event
12+
lastTimestamp: "2022-10-03T14:40:47Z"
13+
message: Compliant; notification - this is the oldest event
14+
metadata:
15+
creationTimestamp: "2022-10-03T14:40:47Z"
16+
name: default.case11-test-policy.a.171a96193d32cf17
17+
reason: 'policy: managed/case11-cfg-policy'
18+
reportingComponent: "filler"
19+
reportingInstance: "filler"
20+
source:
21+
component: configuration-policy-controller
22+
type: Normal

0 commit comments

Comments
 (0)