@@ -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.
@@ -293,7 +296,11 @@ func (d *defaultMergeStrategy) Merge(conditions []ConditionWithOwnerInfo, condit
293
296
// Accordingly, the resulting message is composed by only three messages from conditions classified as issues/unknown;
294
297
// instead three messages from conditions classified as info are included only if there are no issues/unknown.
295
298
//
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
+ //
297
304
// For each message it is reported a list of max 3 objects reporting the message; if more objects are reporting the same
298
305
// message, the number of those objects is surfaced.
299
306
//
@@ -308,22 +315,16 @@ func (d *defaultMergeStrategy) Merge(conditions []ConditionWithOwnerInfo, condit
308
315
n := 3
309
316
messages := []string {}
310
317
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" } )
314
321
messages = append (messages , issueMessages ... )
315
322
}
316
323
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
-
323
324
// Only if there are no issue or unknown,
324
325
// Get max n info messages, decrement n, and track if there are other objects reporting info not included in the messages.
325
326
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" } )
327
328
messages = append (messages , infoMessages ... )
328
329
}
329
330
@@ -369,19 +370,50 @@ func splitConditionsByPriority(conditions []ConditionWithOwnerInfo, getPriority
369
370
}
370
371
371
372
// 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 ) {
373
374
// create a map with all the messages and the list of objects reporting the same message.
374
375
messageObjMap := map [string ]map [string ][]string {}
376
+ messagePriorityMap := map [string ]MergePriority {}
377
+ messageMustGoFirst := map [string ]bool {}
375
378
for _ , condition := range conditions {
376
379
if dropEmpty && condition .Message == "" {
377
380
continue
378
381
}
379
382
383
+ // Keep track of the message and the list of objects it applies to.
380
384
m := condition .Message
381
385
if _ , ok := messageObjMap [condition .OwnerResource .Kind ]; ! ok {
382
386
messageObjMap [condition .OwnerResource .Kind ] = map [string ][]string {}
383
387
}
384
388
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
+ }
385
417
}
386
418
387
419
// Gets the objects kind (with a stable order).
@@ -396,27 +428,29 @@ func aggregateMessages(conditions []ConditionWithOwnerInfo, n *int, dropEmpty bo
396
428
kindPlural := flect .Pluralize (kind )
397
429
messageObjMapForKind := messageObjMap [kind ]
398
430
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.
400
435
// Note: The list of object names is used as a secondary criteria to sort messages with the same number of objects.
401
436
messageIndex := make ([]string , 0 , len (messageObjMapForKind ))
402
437
for m := range messageObjMapForKind {
403
438
messageIndex = append (messageIndex , m )
404
439
}
405
440
406
441
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 )
409
443
})
410
444
411
445
// Pick the first n messages, decrement n.
412
446
// For each message, add up to three objects; if more add the number of the remaining objects with the same message.
413
447
// Count the number of objects reporting messages not included in the above.
414
448
// Note: we are showing up to three objects because usually control plane has 3 machines, and we want to show all issues
415
449
// to control plane machines if any,
416
- var other = 0
450
+ others := map [ MergePriority ] int {}
417
451
for _ , m := range messageIndex {
418
452
if * n == 0 {
419
- other += len (messageObjMapForKind [m ])
453
+ others [ messagePriorityMap [ m ]] += len (messageObjMapForKind [m ])
420
454
continue
421
455
}
422
456
@@ -439,17 +473,53 @@ func aggregateMessages(conditions []ConditionWithOwnerInfo, n *int, dropEmpty bo
439
473
* n --
440
474
}
441
475
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
+ }
447
492
}
448
493
}
449
494
450
495
return messages
451
496
}
452
497
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
+
453
523
func indentIfMultiline (m string ) string {
454
524
msg := ""
455
525
if strings .Contains (m , "\n " ) || strings .HasPrefix (m , "* " ) {
@@ -485,6 +555,7 @@ func getConditionsWithOwnerInfo(obj Getter) []ConditionWithOwnerInfo {
485
555
// is the same as kind.
486
556
func getConditionOwnerInfo (obj any ) ConditionOwnerInfo {
487
557
var kind , name string
558
+ var isControlPlaneMachine bool
488
559
if runtimeObject , ok := obj .(runtime.Object ); ok {
489
560
kind = runtimeObject .GetObjectKind ().GroupVersionKind ().Kind
490
561
}
@@ -498,17 +569,22 @@ func getConditionOwnerInfo(obj any) ConditionOwnerInfo {
498
569
}
499
570
}
500
571
501
- if objMeta , ok := obj .(objectWithName ); ok {
572
+ if objMeta , ok := obj .(objectWithNameAndLabels ); ok {
502
573
name = objMeta .GetName ()
574
+ if kind == "Machine" {
575
+ _ , isControlPlaneMachine = objMeta .GetLabels ()[clusterv1 .MachineControlPlaneLabel ]
576
+ }
503
577
}
504
578
505
579
return ConditionOwnerInfo {
506
- Kind : kind ,
507
- Name : name ,
580
+ Kind : kind ,
581
+ Name : name ,
582
+ IsControlPlaneMachine : isControlPlaneMachine ,
508
583
}
509
584
}
510
585
511
- // objectWithName is a subset of metav1.Object.
512
- type objectWithName interface {
586
+ // objectWithNameAndLabels is a subset of metav1.Object.
587
+ type objectWithNameAndLabels interface {
513
588
GetName () string
589
+ GetLabels () map [string ]string
514
590
}
0 commit comments