Skip to content

Commit be15b0e

Browse files
authored
Merge pull request #184 from andyatmiami/feat/cherry-pick-kueue-checks
feat: add aggressive kueue gating and enhanced verbose output
2 parents 0d9a742 + c5357a0 commit be15b0e

39 files changed

+2579
-2451
lines changed

pkg/lint/check/result/result.go

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,19 @@ const (
1616
// (e.g., "notebooks.kubeflow.org"). Automatically set by SetImpactedObjects and
1717
// AddImpactedObjects from the ResourceType.
1818
AnnotationResourceCRDName = "resource.opendatahub.io/crd-name"
19+
20+
// AnnotationObjectContext is an optional per-object annotation key that provides
21+
// brief clarifying context about why a specific object is impacted. When set on
22+
// an ImpactedObject's ObjectMeta.Annotations, the EnhancedVerboseFormatter renders
23+
// it as a sub-bullet beneath the object reference.
24+
AnnotationObjectContext = "result.opendatahub.io/context"
25+
26+
// AnnotationObjectCRDName is an optional per-object annotation key for the CRD
27+
// fully-qualified name (e.g., "notebooks.kubeflow.org"). Set from ResourceType.CRDFQN()
28+
// when building impacted objects. Used by buildCRDFQNByKind for authoritative plural
29+
// forms, avoiding naive derivation from Kind. Especially useful for multi-kind results
30+
// where the result-level AnnotationResourceCRDName cannot represent all types.
31+
AnnotationObjectCRDName = "result.opendatahub.io/crd-name"
1932
)
2033

2134
const (
@@ -33,11 +46,12 @@ const (
3346
// Impact represents the upgrade impact level of a diagnostic condition.
3447
type Impact string
3548

36-
// Impact levels for diagnostic conditions.
49+
// Impact levels for diagnostic conditions, ordered by severity (highest first).
3750
const (
38-
ImpactBlocking Impact = "blocking" // Upgrade CANNOT proceed
39-
ImpactAdvisory Impact = "advisory" // Upgrade CAN proceed with warning
40-
ImpactNone Impact = "" // No impact (omitted from JSON/YAML)
51+
ImpactProhibited Impact = "prohibited" // Upgrade is NOT POSSIBLE
52+
ImpactBlocking Impact = "blocking" // Upgrade CANNOT proceed
53+
ImpactAdvisory Impact = "advisory" // Upgrade CAN proceed with warning
54+
ImpactNone Impact = "" // No impact (omitted from JSON/YAML)
4155
)
4256

4357
// Condition represents a diagnostic condition with severity level.
@@ -71,16 +85,16 @@ func (c Condition) Validate() error {
7185
// False/Unknown status must have impact specified.
7286
if c.Impact == ImpactNone || c.Impact == "" {
7387
return fmt.Errorf(
74-
"condition with Status=%q must have Impact specified (Blocking or Advisory), got Impact=%q",
88+
"condition with Status=%q must have Impact specified (Prohibited, Blocking, or Advisory), got Impact=%q",
7589
c.Status, c.Impact,
7690
)
7791
}
7892

7993
// Validate impact values.
80-
if c.Impact != ImpactBlocking && c.Impact != ImpactAdvisory {
94+
if c.Impact != ImpactProhibited && c.Impact != ImpactBlocking && c.Impact != ImpactAdvisory {
8195
return fmt.Errorf(
82-
"invalid Impact=%q, must be %q or %q",
83-
c.Impact, ImpactBlocking, ImpactAdvisory,
96+
"invalid Impact=%q, must be %q, %q, or %q",
97+
c.Impact, ImpactProhibited, ImpactBlocking, ImpactAdvisory,
8498
)
8599
}
86100

@@ -254,10 +268,14 @@ func (r *DiagnosticResult) GetImpact() Impact {
254268

255269
for _, cond := range r.Status.Conditions {
256270
switch cond.Impact {
271+
case ImpactProhibited:
272+
return ImpactProhibited
257273
case ImpactBlocking:
258-
return ImpactBlocking
274+
maxImpact = ImpactBlocking
259275
case ImpactAdvisory:
260-
maxImpact = ImpactAdvisory
276+
if maxImpact == ImpactNone {
277+
maxImpact = ImpactAdvisory
278+
}
261279
case ImpactNone:
262280
// No impact - continue checking other conditions
263281
}

pkg/lint/check/result/result_test.go

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,53 @@ func TestDiagnosticResult_Validate_EmptyConditionReason(t *testing.T) {
307307
g.Expect(err.Error()).To(ContainSubstring("has empty reason"))
308308
}
309309

310+
func TestConditionValidate_ProhibitedWithStatusFalse(t *testing.T) {
311+
g := NewWithT(t)
312+
313+
cond := result.Condition{
314+
Condition: metav1.Condition{
315+
Type: "Consistency",
316+
Status: metav1.ConditionFalse,
317+
Reason: "Inconsistent",
318+
},
319+
Impact: result.ImpactProhibited,
320+
}
321+
322+
g.Expect(cond.Validate()).ToNot(HaveOccurred())
323+
}
324+
325+
func TestConditionValidate_ProhibitedWithStatusUnknown(t *testing.T) {
326+
g := NewWithT(t)
327+
328+
cond := result.Condition{
329+
Condition: metav1.Condition{
330+
Type: "Consistency",
331+
Status: metav1.ConditionUnknown,
332+
Reason: "CannotDetermine",
333+
},
334+
Impact: result.ImpactProhibited,
335+
}
336+
337+
g.Expect(cond.Validate()).ToNot(HaveOccurred())
338+
}
339+
340+
func TestConditionValidate_ProhibitedWithStatusTrue_Invalid(t *testing.T) {
341+
g := NewWithT(t)
342+
343+
cond := result.Condition{
344+
Condition: metav1.Condition{
345+
Type: "Consistency",
346+
Status: metav1.ConditionTrue,
347+
Reason: "Consistent",
348+
},
349+
Impact: result.ImpactProhibited,
350+
}
351+
352+
err := cond.Validate()
353+
g.Expect(err).To(HaveOccurred())
354+
g.Expect(err.Error()).To(ContainSubstring("Status=True must have Impact=None"))
355+
}
356+
310357
// T023: Multiple conditions array tests
311358

312359
func TestMultipleConditions_OrderedByExecutionSequence(t *testing.T) {
@@ -790,6 +837,118 @@ func TestSetCondition_MultipleConditionTypes(t *testing.T) {
790837
}))
791838
}
792839

840+
// GetImpact tests
841+
842+
func TestGetImpact_ReturnsProhibitedAsHighest(t *testing.T) {
843+
g := NewWithT(t)
844+
845+
dr := result.New("workload", "kueue", "data-integrity", "test")
846+
dr.SetCondition(check.NewCondition(
847+
"Advisory",
848+
metav1.ConditionFalse,
849+
check.WithReason("Reason"),
850+
check.WithMessage("advisory finding"),
851+
check.WithImpact(result.ImpactAdvisory),
852+
))
853+
dr.SetCondition(check.NewCondition(
854+
"Blocking",
855+
metav1.ConditionFalse,
856+
check.WithReason("Reason"),
857+
check.WithMessage("blocking finding"),
858+
check.WithImpact(result.ImpactBlocking),
859+
))
860+
dr.SetCondition(check.NewCondition(
861+
"Prohibited",
862+
metav1.ConditionFalse,
863+
check.WithReason("Reason"),
864+
check.WithMessage("prohibited finding"),
865+
check.WithImpact(result.ImpactProhibited),
866+
))
867+
868+
g.Expect(dr.GetImpact()).To(Equal(result.ImpactProhibited))
869+
}
870+
871+
func TestGetImpact_ProhibitedShortCircuits(t *testing.T) {
872+
g := NewWithT(t)
873+
874+
// Prohibited condition first — should return immediately without checking others.
875+
dr := result.New("workload", "kueue", "data-integrity", "test")
876+
dr.SetCondition(check.NewCondition(
877+
"Prohibited",
878+
metav1.ConditionFalse,
879+
check.WithReason("Reason"),
880+
check.WithMessage("prohibited finding"),
881+
check.WithImpact(result.ImpactProhibited),
882+
))
883+
dr.SetCondition(check.NewCondition(
884+
"Passing",
885+
metav1.ConditionTrue,
886+
check.WithReason("Reason"),
887+
check.WithMessage("all good"),
888+
))
889+
890+
g.Expect(dr.GetImpact()).To(Equal(result.ImpactProhibited))
891+
}
892+
893+
func TestGetImpact_BlockingWithoutProhibited(t *testing.T) {
894+
g := NewWithT(t)
895+
896+
dr := result.New("component", "kserve", "removal", "test")
897+
dr.SetCondition(check.NewCondition(
898+
"Blocking",
899+
metav1.ConditionFalse,
900+
check.WithReason("Reason"),
901+
check.WithMessage("blocking finding"),
902+
check.WithImpact(result.ImpactBlocking),
903+
))
904+
dr.SetCondition(check.NewCondition(
905+
"Advisory",
906+
metav1.ConditionFalse,
907+
check.WithReason("Reason"),
908+
check.WithMessage("advisory finding"),
909+
check.WithImpact(result.ImpactAdvisory),
910+
))
911+
912+
g.Expect(dr.GetImpact()).To(Equal(result.ImpactBlocking))
913+
}
914+
915+
func TestGetImpact_AdvisoryWithoutBlockingOrProhibited(t *testing.T) {
916+
g := NewWithT(t)
917+
918+
dr := result.New("component", "dashboard", "migration", "test")
919+
dr.SetCondition(check.NewCondition(
920+
"Advisory",
921+
metav1.ConditionFalse,
922+
check.WithReason("Reason"),
923+
check.WithMessage("advisory finding"),
924+
check.WithImpact(result.ImpactAdvisory),
925+
))
926+
927+
g.Expect(dr.GetImpact()).To(Equal(result.ImpactAdvisory))
928+
}
929+
930+
func TestGetImpact_NoneWhenAllPassing(t *testing.T) {
931+
g := NewWithT(t)
932+
933+
dr := result.New("component", "dashboard", "status", "test")
934+
dr.SetCondition(check.NewCondition(
935+
"Available",
936+
metav1.ConditionTrue,
937+
check.WithReason("Ready"),
938+
check.WithMessage("all good"),
939+
))
940+
941+
g.Expect(dr.GetImpact()).To(Equal(result.ImpactNone))
942+
}
943+
944+
func TestGetImpact_NoneWhenNoConditions(t *testing.T) {
945+
g := NewWithT(t)
946+
947+
dr := result.New("component", "test", "check", "test")
948+
949+
g.Expect(dr.GetImpact()).To(Equal(result.ImpactNone))
950+
}
951+
793952
// SetImpactedObjects and AddImpactedObjects tests
794953

795954
func TestSetImpactedObjects(t *testing.T) {

0 commit comments

Comments
 (0)