Skip to content

Commit 1c3f09e

Browse files
Refine v1beta2 aggregation order
1 parent 029b8c9 commit 1c3f09e

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.
@@ -291,7 +294,11 @@ func (d *defaultMergeStrategy) Merge(conditions []ConditionWithOwnerInfo, condit
291294
// Accordingly, the resulting message is composed by only three messages from conditions classified as issues/unknown;
292295
// instead three messages from conditions classified as info are included only if there are no issues/unknown.
293296
//
294-
// The number of objects reporting the same message determine the order used to pick the messages to be shown;
297+
// Three criteria are used to pick the messages to be shown
298+
// - Messages for control plane machines always go first
299+
// - Messages for issues always go before messages for unknown, info messages goes last
300+
// - The number of objects reporting the same message determine the order used to pick within the messages in the same bucket
301+
//
295302
// For each message it is reported a list of max 3 objects reporting the message; if more objects are reporting the same
296303
// message, the number of those objects is surfaced.
297304
//
@@ -306,22 +313,16 @@ func (d *defaultMergeStrategy) Merge(conditions []ConditionWithOwnerInfo, condit
306313
n := 3
307314
messages := []string{}
308315

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

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

@@ -367,19 +368,50 @@ func splitConditionsByPriority(conditions []ConditionWithOwnerInfo, getPriority
367368
}
368369

369370
// aggregateMessages returns messages for the aggregate operation.
370-
func aggregateMessages(conditions []ConditionWithOwnerInfo, n *int, dropEmpty bool, otherMessage string) (messages []string) {
371+
func aggregateMessages(conditions []ConditionWithOwnerInfo, n *int, dropEmpty bool, getPriority func(condition metav1.Condition) MergePriority, otherMessages map[MergePriority]string) (messages []string) {
371372
// create a map with all the messages and the list of objects reporting the same message.
372373
messageObjMap := map[string]map[string][]string{}
374+
messagePriorityMap := map[string]MergePriority{}
375+
messageMustGoFirst := map[string]bool{}
373376
for _, condition := range conditions {
374377
if dropEmpty && condition.Message == "" {
375378
continue
376379
}
377380

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

385417
// Gets the objects kind (with a stable order).
@@ -394,27 +426,29 @@ func aggregateMessages(conditions []ConditionWithOwnerInfo, n *int, dropEmpty bo
394426
kindPlural := flect.Pluralize(kind)
395427
messageObjMapForKind := messageObjMap[kind]
396428

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

404439
sort.SliceStable(messageIndex, func(i, j int) bool {
405-
return len(messageObjMapForKind[messageIndex[i]]) > len(messageObjMapForKind[messageIndex[j]]) ||
406-
(len(messageObjMapForKind[messageIndex[i]]) == len(messageObjMapForKind[messageIndex[j]]) && strings.Join(messageObjMapForKind[messageIndex[i]], ",") < strings.Join(messageObjMapForKind[messageIndex[j]], ","))
440+
return sortMessage(messageIndex[i], messageIndex[j], messageMustGoFirst, messagePriorityMap, messageObjMapForKind)
407441
})
408442

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

@@ -437,17 +471,53 @@ func aggregateMessages(conditions []ConditionWithOwnerInfo, n *int, dropEmpty bo
437471
*n--
438472
}
439473

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

448493
return messages
449494
}
450495

496+
func sortMessage(i, j string, messageMustGoFirst map[string]bool, messagePriorityMap map[string]MergePriority, messageObjMapForKind map[string][]string) bool {
497+
if messageMustGoFirst[i] && !messageMustGoFirst[j] {
498+
return true
499+
}
500+
if !messageMustGoFirst[i] && messageMustGoFirst[j] {
501+
return false
502+
}
503+
504+
if messagePriorityMap[i] < messagePriorityMap[j] {
505+
return true
506+
}
507+
if messagePriorityMap[i] > messagePriorityMap[j] {
508+
return false
509+
}
510+
511+
if len(messageObjMapForKind[i]) > len(messageObjMapForKind[j]) {
512+
return true
513+
}
514+
if len(messageObjMapForKind[i]) < len(messageObjMapForKind[j]) {
515+
return false
516+
}
517+
518+
return strings.Join(messageObjMapForKind[i], ",") < strings.Join(messageObjMapForKind[j], ",")
519+
}
520+
451521
func indentIfMultiline(m string) string {
452522
msg := ""
453523
if strings.Contains(m, "\n") || strings.HasPrefix(m, "* ") {
@@ -483,6 +553,7 @@ func getConditionsWithOwnerInfo(obj Getter) []ConditionWithOwnerInfo {
483553
// is the same as kind.
484554
func getConditionOwnerInfo(obj any) ConditionOwnerInfo {
485555
var kind, name string
556+
var isControlPlaneMachine bool
486557
if runtimeObject, ok := obj.(runtime.Object); ok {
487558
kind = runtimeObject.GetObjectKind().GroupVersionKind().Kind
488559
}
@@ -496,17 +567,22 @@ func getConditionOwnerInfo(obj any) ConditionOwnerInfo {
496567
}
497568
}
498569

499-
if objMeta, ok := obj.(objectWithName); ok {
570+
if objMeta, ok := obj.(objectWithNameAndLabels); ok {
500571
name = objMeta.GetName()
572+
if kind == "Machine" {
573+
_, isControlPlaneMachine = objMeta.GetLabels()[clusterv1.MachineControlPlaneLabel]
574+
}
501575
}
502576

503577
return ConditionOwnerInfo{
504-
Kind: kind,
505-
Name: name,
578+
Kind: kind,
579+
Name: name,
580+
IsControlPlaneMachine: isControlPlaneMachine,
506581
}
507582
}
508583

509-
// objectWithName is a subset of metav1.Object.
510-
type objectWithName interface {
584+
// objectWithNameAndLabels is a subset of metav1.Object.
585+
type objectWithNameAndLabels interface {
511586
GetName() string
587+
GetLabels() map[string]string
512588
}

0 commit comments

Comments
 (0)