Skip to content

Commit 7dd613c

Browse files
authored
Merge pull request #11423 from fabriziopandini/refine-v1beta2-aggregation-order
🌱 Refine v1beta2 aggregation order
2 parents 438d159 + 1c3f09e commit 7dd613c

File tree

4 files changed

+301
-59
lines changed

4 files changed

+301
-59
lines changed

internal/controllers/cluster/cluster_controller_status.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1117,3 +1117,17 @@ func (w aggregationWrapper) GetName() string {
11171117
}
11181118
panic("not supported")
11191119
}
1120+
1121+
func (w aggregationWrapper) GetLabels() map[string]string {
1122+
switch {
1123+
case w.cp != nil:
1124+
return w.cp.GetLabels()
1125+
case w.mp != nil:
1126+
return w.mp.GetLabels()
1127+
case w.md != nil:
1128+
return w.md.GetLabels()
1129+
case w.ms != nil:
1130+
return w.ms.GetLabels()
1131+
}
1132+
panic("not supported")
1133+
}

internal/controllers/cluster/cluster_controller_status_test.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -865,6 +865,39 @@ func TestSetMachinesReadyCondition(t *testing.T) {
865865
"* Machine machine-3: Some unknown message",
866866
},
867867
},
868+
{
869+
name: "control plane goes first",
870+
cluster: fakeCluster("c"),
871+
machines: []*clusterv1.Machine{
872+
fakeMachine("machine-1", v1beta2Condition(readyCondition)),
873+
fakeMachine("machine-2", v1beta2Condition(metav1.Condition{
874+
Type: clusterv1.MachineReadyV1Beta2Condition,
875+
Status: metav1.ConditionFalse,
876+
Reason: clusterv1.MachineDeletingV1Beta2Reason,
877+
Message: "Deleting: Machine deletion in progress, stage: DrainingNode",
878+
})),
879+
fakeMachine("machine-3", controlPlane(true), v1beta2Condition(metav1.Condition{ // control plane always must go first
880+
Type: clusterv1.MachineReadyV1Beta2Condition,
881+
Status: metav1.ConditionUnknown,
882+
Reason: "SomeUnknownReason",
883+
Message: "Some unknown message",
884+
})),
885+
fakeMachine("machine-4", v1beta2Condition(metav1.Condition{
886+
Type: clusterv1.MachineReadyV1Beta2Condition,
887+
Status: metav1.ConditionFalse,
888+
Reason: clusterv1.MachineDeletingV1Beta2Reason,
889+
Message: "Deleting: Machine deletion in progress, stage: DrainingNode",
890+
})),
891+
},
892+
getDescendantsSucceeded: true,
893+
expectCondition: metav1.Condition{
894+
Type: clusterv1.ClusterMachinesReadyV1Beta2Condition,
895+
Status: metav1.ConditionFalse,
896+
Reason: clusterv1.ClusterMachinesNotReadyV1Beta2Reason,
897+
Message: "* Machine machine-3: Some unknown message\n" +
898+
"* Machines machine-2, machine-4: Deleting: Machine deletion in progress, stage: DrainingNode",
899+
},
900+
},
868901
}
869902
for _, tt := range tests {
870903
t.Run(tt.name, func(t *testing.T) {
@@ -2628,3 +2661,16 @@ type topology bool
26282661
func (r topology) ApplyToCluster(c *clusterv1.Cluster) {
26292662
c.Spec.Topology = &clusterv1.Topology{}
26302663
}
2664+
2665+
type controlPlane bool
2666+
2667+
func (c controlPlane) ApplyToMachine(m *clusterv1.Machine) {
2668+
if c {
2669+
labels := m.GetLabels()
2670+
if labels == nil {
2671+
labels = map[string]string{}
2672+
}
2673+
labels[clusterv1.MachineControlPlaneLabel] = ""
2674+
m.SetLabels(labels)
2675+
}
2676+
}

util/conditions/v1beta2/merge_strategies.go

Lines changed: 105 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ import (
2727
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2828
"k8s.io/apimachinery/pkg/runtime"
2929
"k8s.io/apimachinery/pkg/util/sets"
30+
31+
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
3032
)
3133

3234
// ConditionWithOwnerInfo is a wrapper around metav1.Condition with additional ConditionOwnerInfo.
@@ -38,8 +40,9 @@ type ConditionWithOwnerInfo struct {
3840

3941
// ConditionOwnerInfo contains infos about the object that owns the condition.
4042
type ConditionOwnerInfo struct {
41-
Kind string
42-
Name string
43+
Kind string
44+
Name string
45+
IsControlPlaneMachine bool
4346
}
4447

4548
// String returns a string representation of the ConditionOwnerInfo.
@@ -293,7 +296,11 @@ func (d *defaultMergeStrategy) Merge(conditions []ConditionWithOwnerInfo, condit
293296
// Accordingly, the resulting message is composed by only three messages from conditions classified as issues/unknown;
294297
// instead three messages from conditions classified as info are included only if there are no issues/unknown.
295298
//
296-
// The number of objects reporting the same message determine the order used to pick the messages to be shown;
299+
// Three criteria are used to pick the messages to be shown
300+
// - Messages for control plane machines always go first
301+
// - Messages for issues always go before messages for unknown, info messages goes last
302+
// - The number of objects reporting the same message determine the order used to pick within the messages in the same bucket
303+
//
297304
// For each message it is reported a list of max 3 objects reporting the message; if more objects are reporting the same
298305
// message, the number of those objects is surfaced.
299306
//
@@ -308,22 +315,16 @@ func (d *defaultMergeStrategy) Merge(conditions []ConditionWithOwnerInfo, condit
308315
n := 3
309316
messages := []string{}
310317

311-
// Get max n issue messages, decrement n, and track if there are other objects reporting issues not included in the messages.
312-
if len(issueConditions) > 0 {
313-
issueMessages := aggregateMessages(issueConditions, &n, false, "with other issues")
318+
// Get max n issue/unknown messages, decrement n, and track if there are other objects reporting issues/unknown not included in the messages.
319+
if len(issueConditions) > 0 || len(unknownConditions) > 0 {
320+
issueMessages := aggregateMessages(append(issueConditions, unknownConditions...), &n, false, d.getPriorityFunc, map[MergePriority]string{IssueMergePriority: "with other issues", UnknownMergePriority: "with status unknown"})
314321
messages = append(messages, issueMessages...)
315322
}
316323

317-
// Get max n unknown messages, decrement n, and track if there are other objects reporting unknown not included in the messages.
318-
if len(unknownConditions) > 0 {
319-
unknownMessages := aggregateMessages(unknownConditions, &n, false, "with status unknown")
320-
messages = append(messages, unknownMessages...)
321-
}
322-
323324
// Only if there are no issue or unknown,
324325
// Get max n info messages, decrement n, and track if there are other objects reporting info not included in the messages.
325326
if len(issueConditions) == 0 && len(unknownConditions) == 0 && len(infoConditions) > 0 {
326-
infoMessages := aggregateMessages(infoConditions, &n, true, "with additional info")
327+
infoMessages := aggregateMessages(infoConditions, &n, true, d.getPriorityFunc, map[MergePriority]string{InfoMergePriority: "with additional info"})
327328
messages = append(messages, infoMessages...)
328329
}
329330

@@ -369,19 +370,50 @@ func splitConditionsByPriority(conditions []ConditionWithOwnerInfo, getPriority
369370
}
370371

371372
// aggregateMessages returns messages for the aggregate operation.
372-
func aggregateMessages(conditions []ConditionWithOwnerInfo, n *int, dropEmpty bool, otherMessage string) (messages []string) {
373+
func aggregateMessages(conditions []ConditionWithOwnerInfo, n *int, dropEmpty bool, getPriority func(condition metav1.Condition) MergePriority, otherMessages map[MergePriority]string) (messages []string) {
373374
// create a map with all the messages and the list of objects reporting the same message.
374375
messageObjMap := map[string]map[string][]string{}
376+
messagePriorityMap := map[string]MergePriority{}
377+
messageMustGoFirst := map[string]bool{}
375378
for _, condition := range conditions {
376379
if dropEmpty && condition.Message == "" {
377380
continue
378381
}
379382

383+
// Keep track of the message and the list of objects it applies to.
380384
m := condition.Message
381385
if _, ok := messageObjMap[condition.OwnerResource.Kind]; !ok {
382386
messageObjMap[condition.OwnerResource.Kind] = map[string][]string{}
383387
}
384388
messageObjMap[condition.OwnerResource.Kind][m] = append(messageObjMap[condition.OwnerResource.Kind][m], condition.OwnerResource.Name)
389+
390+
// Keep track of the priority of the message.
391+
// In case the same message exists with different priorities, the highest according to issue/unknown/info applies.
392+
currentPriority, ok := messagePriorityMap[m]
393+
newPriority := getPriority(condition.Condition)
394+
switch {
395+
case !ok:
396+
messagePriorityMap[m] = newPriority
397+
case currentPriority == IssueMergePriority:
398+
// No-op, issue is already the highest priority.
399+
case currentPriority == UnknownMergePriority:
400+
// If current priority is unknown, use new one only if higher.
401+
if newPriority == IssueMergePriority {
402+
messagePriorityMap[m] = newPriority
403+
}
404+
case currentPriority == InfoMergePriority:
405+
// if current priority is info, new one can be equal or higher, use it.
406+
messagePriorityMap[m] = newPriority
407+
}
408+
409+
// Keep track if this message belongs to control plane machines, and thus it should go first.
410+
// Note: it is enough that on object is a control plane machine to move the message as first.
411+
first, ok := messageMustGoFirst[m]
412+
if !ok || !first {
413+
if condition.OwnerResource.IsControlPlaneMachine {
414+
messageMustGoFirst[m] = true
415+
}
416+
}
385417
}
386418

387419
// Gets the objects kind (with a stable order).
@@ -396,27 +428,29 @@ func aggregateMessages(conditions []ConditionWithOwnerInfo, n *int, dropEmpty bo
396428
kindPlural := flect.Pluralize(kind)
397429
messageObjMapForKind := messageObjMap[kind]
398430

399-
// compute the order of messages according to the number of objects reporting the same message.
431+
// compute the order of messages according to:
432+
// - message should go first (e.g. it applies to a control plane machine)
433+
// - message priority (e.g. first issues, then unknown)
434+
// - the number of objects reporting the same message.
400435
// Note: The list of object names is used as a secondary criteria to sort messages with the same number of objects.
401436
messageIndex := make([]string, 0, len(messageObjMapForKind))
402437
for m := range messageObjMapForKind {
403438
messageIndex = append(messageIndex, m)
404439
}
405440

406441
sort.SliceStable(messageIndex, func(i, j int) bool {
407-
return len(messageObjMapForKind[messageIndex[i]]) > len(messageObjMapForKind[messageIndex[j]]) ||
408-
(len(messageObjMapForKind[messageIndex[i]]) == len(messageObjMapForKind[messageIndex[j]]) && strings.Join(messageObjMapForKind[messageIndex[i]], ",") < strings.Join(messageObjMapForKind[messageIndex[j]], ","))
442+
return sortMessage(messageIndex[i], messageIndex[j], messageMustGoFirst, messagePriorityMap, messageObjMapForKind)
409443
})
410444

411445
// Pick the first n messages, decrement n.
412446
// For each message, add up to three objects; if more add the number of the remaining objects with the same message.
413447
// Count the number of objects reporting messages not included in the above.
414448
// Note: we are showing up to three objects because usually control plane has 3 machines, and we want to show all issues
415449
// to control plane machines if any,
416-
var other = 0
450+
others := map[MergePriority]int{}
417451
for _, m := range messageIndex {
418452
if *n == 0 {
419-
other += len(messageObjMapForKind[m])
453+
others[messagePriorityMap[m]] += len(messageObjMapForKind[m])
420454
continue
421455
}
422456

@@ -439,17 +473,53 @@ func aggregateMessages(conditions []ConditionWithOwnerInfo, n *int, dropEmpty bo
439473
*n--
440474
}
441475

442-
if other == 1 {
443-
messages = append(messages, fmt.Sprintf("And %d %s %s", other, kind, otherMessage))
444-
}
445-
if other > 1 {
446-
messages = append(messages, fmt.Sprintf("And %d %s %s", other, kindPlural, otherMessage))
476+
for _, p := range []MergePriority{IssueMergePriority, UnknownMergePriority, InfoMergePriority} {
477+
other, ok := others[p]
478+
if !ok {
479+
continue
480+
}
481+
482+
otherMessage, ok := otherMessages[p]
483+
if !ok {
484+
continue
485+
}
486+
if other == 1 {
487+
messages = append(messages, fmt.Sprintf("And %d %s %s", other, kind, otherMessage))
488+
}
489+
if other > 1 {
490+
messages = append(messages, fmt.Sprintf("And %d %s %s", other, kindPlural, otherMessage))
491+
}
447492
}
448493
}
449494

450495
return messages
451496
}
452497

498+
func sortMessage(i, j string, messageMustGoFirst map[string]bool, messagePriorityMap map[string]MergePriority, messageObjMapForKind map[string][]string) bool {
499+
if messageMustGoFirst[i] && !messageMustGoFirst[j] {
500+
return true
501+
}
502+
if !messageMustGoFirst[i] && messageMustGoFirst[j] {
503+
return false
504+
}
505+
506+
if messagePriorityMap[i] < messagePriorityMap[j] {
507+
return true
508+
}
509+
if messagePriorityMap[i] > messagePriorityMap[j] {
510+
return false
511+
}
512+
513+
if len(messageObjMapForKind[i]) > len(messageObjMapForKind[j]) {
514+
return true
515+
}
516+
if len(messageObjMapForKind[i]) < len(messageObjMapForKind[j]) {
517+
return false
518+
}
519+
520+
return strings.Join(messageObjMapForKind[i], ",") < strings.Join(messageObjMapForKind[j], ",")
521+
}
522+
453523
func indentIfMultiline(m string) string {
454524
msg := ""
455525
if strings.Contains(m, "\n") || strings.HasPrefix(m, "* ") {
@@ -485,6 +555,7 @@ func getConditionsWithOwnerInfo(obj Getter) []ConditionWithOwnerInfo {
485555
// is the same as kind.
486556
func getConditionOwnerInfo(obj any) ConditionOwnerInfo {
487557
var kind, name string
558+
var isControlPlaneMachine bool
488559
if runtimeObject, ok := obj.(runtime.Object); ok {
489560
kind = runtimeObject.GetObjectKind().GroupVersionKind().Kind
490561
}
@@ -498,17 +569,22 @@ func getConditionOwnerInfo(obj any) ConditionOwnerInfo {
498569
}
499570
}
500571

501-
if objMeta, ok := obj.(objectWithName); ok {
572+
if objMeta, ok := obj.(objectWithNameAndLabels); ok {
502573
name = objMeta.GetName()
574+
if kind == "Machine" {
575+
_, isControlPlaneMachine = objMeta.GetLabels()[clusterv1.MachineControlPlaneLabel]
576+
}
503577
}
504578

505579
return ConditionOwnerInfo{
506-
Kind: kind,
507-
Name: name,
580+
Kind: kind,
581+
Name: name,
582+
IsControlPlaneMachine: isControlPlaneMachine,
508583
}
509584
}
510585

511-
// objectWithName is a subset of metav1.Object.
512-
type objectWithName interface {
586+
// objectWithNameAndLabels is a subset of metav1.Object.
587+
type objectWithNameAndLabels interface {
513588
GetName() string
589+
GetLabels() map[string]string
514590
}

0 commit comments

Comments
 (0)