@@ -27,6 +27,8 @@ import (
27
27
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
28
28
"k8s.io/apimachinery/pkg/runtime"
29
29
"k8s.io/apimachinery/pkg/util/sets"
30
+
31
+ clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
30
32
)
31
33
32
34
// ConditionWithOwnerInfo is a wrapper around metav1.Condition with additional ConditionOwnerInfo.
@@ -38,8 +40,9 @@ type ConditionWithOwnerInfo struct {
38
40
39
41
// ConditionOwnerInfo contains infos about the object that owns the condition.
40
42
type ConditionOwnerInfo struct {
41
- Kind string
42
- Name string
43
+ Kind string
44
+ Name string
45
+ IsControlPlaneMachine bool
43
46
}
44
47
45
48
// String returns a string representation of the ConditionOwnerInfo.
@@ -291,7 +294,11 @@ func (d *defaultMergeStrategy) Merge(conditions []ConditionWithOwnerInfo, condit
291
294
// Accordingly, the resulting message is composed by only three messages from conditions classified as issues/unknown;
292
295
// instead three messages from conditions classified as info are included only if there are no issues/unknown.
293
296
//
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
+ //
295
302
// For each message it is reported a list of max 3 objects reporting the message; if more objects are reporting the same
296
303
// message, the number of those objects is surfaced.
297
304
//
@@ -306,22 +313,16 @@ func (d *defaultMergeStrategy) Merge(conditions []ConditionWithOwnerInfo, condit
306
313
n := 3
307
314
messages := []string {}
308
315
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" } )
312
319
messages = append (messages , issueMessages ... )
313
320
}
314
321
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
-
321
322
// Only if there are no issue or unknown,
322
323
// Get max n info messages, decrement n, and track if there are other objects reporting info not included in the messages.
323
324
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" } )
325
326
messages = append (messages , infoMessages ... )
326
327
}
327
328
@@ -367,19 +368,50 @@ func splitConditionsByPriority(conditions []ConditionWithOwnerInfo, getPriority
367
368
}
368
369
369
370
// 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 ) {
371
372
// create a map with all the messages and the list of objects reporting the same message.
372
373
messageObjMap := map [string ]map [string ][]string {}
374
+ messagePriorityMap := map [string ]MergePriority {}
375
+ messageMustGoFirst := map [string ]bool {}
373
376
for _ , condition := range conditions {
374
377
if dropEmpty && condition .Message == "" {
375
378
continue
376
379
}
377
380
381
+ // Keep track of the message and the list of objects it applies to.
378
382
m := condition .Message
379
383
if _ , ok := messageObjMap [condition .OwnerResource .Kind ]; ! ok {
380
384
messageObjMap [condition .OwnerResource .Kind ] = map [string ][]string {}
381
385
}
382
386
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
+ }
383
415
}
384
416
385
417
// Gets the objects kind (with a stable order).
@@ -394,27 +426,29 @@ func aggregateMessages(conditions []ConditionWithOwnerInfo, n *int, dropEmpty bo
394
426
kindPlural := flect .Pluralize (kind )
395
427
messageObjMapForKind := messageObjMap [kind ]
396
428
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.
398
433
// Note: The list of object names is used as a secondary criteria to sort messages with the same number of objects.
399
434
messageIndex := make ([]string , 0 , len (messageObjMapForKind ))
400
435
for m := range messageObjMapForKind {
401
436
messageIndex = append (messageIndex , m )
402
437
}
403
438
404
439
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 )
407
441
})
408
442
409
443
// Pick the first n messages, decrement n.
410
444
// For each message, add up to three objects; if more add the number of the remaining objects with the same message.
411
445
// Count the number of objects reporting messages not included in the above.
412
446
// Note: we are showing up to three objects because usually control plane has 3 machines, and we want to show all issues
413
447
// to control plane machines if any,
414
- var other = 0
448
+ others := map [ MergePriority ] int {}
415
449
for _ , m := range messageIndex {
416
450
if * n == 0 {
417
- other += len (messageObjMapForKind [m ])
451
+ others [ messagePriorityMap [ m ]] += len (messageObjMapForKind [m ])
418
452
continue
419
453
}
420
454
@@ -437,17 +471,53 @@ func aggregateMessages(conditions []ConditionWithOwnerInfo, n *int, dropEmpty bo
437
471
* n --
438
472
}
439
473
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
+ }
445
490
}
446
491
}
447
492
448
493
return messages
449
494
}
450
495
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
+
451
521
func indentIfMultiline (m string ) string {
452
522
msg := ""
453
523
if strings .Contains (m , "\n " ) || strings .HasPrefix (m , "* " ) {
@@ -483,6 +553,7 @@ func getConditionsWithOwnerInfo(obj Getter) []ConditionWithOwnerInfo {
483
553
// is the same as kind.
484
554
func getConditionOwnerInfo (obj any ) ConditionOwnerInfo {
485
555
var kind , name string
556
+ var isControlPlaneMachine bool
486
557
if runtimeObject , ok := obj .(runtime.Object ); ok {
487
558
kind = runtimeObject .GetObjectKind ().GroupVersionKind ().Kind
488
559
}
@@ -496,17 +567,22 @@ func getConditionOwnerInfo(obj any) ConditionOwnerInfo {
496
567
}
497
568
}
498
569
499
- if objMeta , ok := obj .(objectWithName ); ok {
570
+ if objMeta , ok := obj .(objectWithNameAndLabels ); ok {
500
571
name = objMeta .GetName ()
572
+ if kind == "Machine" {
573
+ _ , isControlPlaneMachine = objMeta .GetLabels ()[clusterv1 .MachineControlPlaneLabel ]
574
+ }
501
575
}
502
576
503
577
return ConditionOwnerInfo {
504
- Kind : kind ,
505
- Name : name ,
578
+ Kind : kind ,
579
+ Name : name ,
580
+ IsControlPlaneMachine : isControlPlaneMachine ,
506
581
}
507
582
}
508
583
509
- // objectWithName is a subset of metav1.Object.
510
- type objectWithName interface {
584
+ // objectWithNameAndLabels is a subset of metav1.Object.
585
+ type objectWithNameAndLabels interface {
511
586
GetName () string
587
+ GetLabels () map [string ]string
512
588
}
0 commit comments