Skip to content

Commit 86b0305

Browse files
Merge pull request #1324 from hongkailiu/evaluateConditionalUpdate
OTA-1546: Generate riskConditions in a better way
2 parents 182fcb6 + 7389850 commit 86b0305

6 files changed

Lines changed: 263 additions & 140 deletions

File tree

pkg/cvo/availableupdates.go

Lines changed: 87 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,6 @@ func (optr *Operator) syncAvailableUpdates(ctx context.Context, config *configv1
144144
optrAvailableUpdates.Architecture = desiredArch
145145
optrAvailableUpdates.ShouldReconcileAcceptRisks = optr.shouldReconcileAcceptRisks
146146
optrAvailableUpdates.AcceptRisks = acceptRisks
147-
optrAvailableUpdates.RiskConditions = map[string][]metav1.Condition{}
148147
optrAvailableUpdates.ConditionRegistry = optr.conditionRegistry
149148
optrAvailableUpdates.Condition = condition
150149

@@ -342,6 +341,76 @@ func (optr *Operator) getAvailableUpdates() *availableUpdates {
342341
return u
343342
}
344343

344+
func loadRiskConditions(ctx context.Context, risks []string, riskVersions map[string]riskWithVersion, conditionRegistry clusterconditions.ConditionRegistry) map[string][]metav1.Condition {
345+
riskConditions := map[string][]metav1.Condition{}
346+
for _, riskName := range risks {
347+
risk := riskVersions[riskName].risk
348+
riskCondition := metav1.Condition{
349+
Type: internal.ConditionalUpdateRiskConditionTypeApplies,
350+
Status: metav1.ConditionFalse,
351+
Reason: riskConditionReasonNotMatch,
352+
}
353+
if match, err := conditionRegistry.Match(ctx, risk.MatchingRules); err != nil {
354+
msg := unknownExposureMessage(risk, err)
355+
riskCondition.Status = metav1.ConditionUnknown
356+
riskCondition.Reason = riskConditionReasonEvaluationFailed
357+
riskCondition.Message = msg
358+
} else if match {
359+
riskCondition.Status = metav1.ConditionTrue
360+
riskCondition.Reason = riskConditionReasonMatch
361+
}
362+
riskConditions[risk.Name] = []metav1.Condition{riskCondition}
363+
}
364+
if len(riskConditions) == 0 {
365+
return nil
366+
}
367+
return riskConditions
368+
}
369+
370+
// risksInOrder returns the list of risk names sorted by the associated version in descending order.
371+
// When versions match, it is sorted by the risk names in ascending order.
372+
func risksInOrder(riskVersions map[string]riskWithVersion) []string {
373+
var ret []string
374+
var temp []riskWithVersion
375+
for k := range riskVersions {
376+
temp = append(temp, riskVersions[k])
377+
}
378+
sort.SliceStable(temp, func(i, j int) bool {
379+
return temp[i].version.GT(temp[j].version) || temp[i].version.EQ(temp[j].version) && temp[i].risk.Name < temp[j].risk.Name
380+
})
381+
for _, v := range temp {
382+
ret = append(ret, v.risk.Name)
383+
}
384+
return ret
385+
}
386+
387+
type riskWithVersion struct {
388+
version semver.Version
389+
risk configv1.ConditionalUpdateRisk
390+
}
391+
392+
// loadRiskVersions returns the map from risk names to riskWithVersion where
393+
// riskWithVersion.version is the latest version that the risk is exposed to
394+
// and riskWithVersion.risk is the risk itself
395+
func loadRiskVersions(conditionalUpdates []configv1.ConditionalUpdate) map[string]riskWithVersion {
396+
riskVersions := map[string]riskWithVersion{}
397+
for _, conditionalUpdate := range conditionalUpdates {
398+
for _, risk := range conditionalUpdate.Risks {
399+
candidate := semver.MustParse(conditionalUpdate.Release.Version)
400+
if v, ok := riskVersions[risk.Name]; !ok || candidate.GT(v.version) {
401+
riskVersions[risk.Name] = riskWithVersion{
402+
version: candidate,
403+
risk: risk,
404+
}
405+
}
406+
}
407+
}
408+
if len(riskVersions) == 0 {
409+
return nil
410+
}
411+
return riskVersions
412+
}
413+
345414
func (optr *Operator) getDesiredArchitecture(update *configv1.Update) string {
346415
if update != nil && len(update.Architecture) > 0 {
347416
return string(update.Architecture)
@@ -445,16 +514,15 @@ func (u *availableUpdates) evaluateConditionalUpdates(ctx context.Context) {
445514
return
446515
}
447516

448-
sort.Slice(u.ConditionalUpdates, func(i, j int) bool {
449-
vi := semver.MustParse(u.ConditionalUpdates[i].Release.Version)
450-
vj := semver.MustParse(u.ConditionalUpdates[j].Release.Version)
451-
return vi.GTE(vj)
452-
})
517+
riskVersions := loadRiskVersions(u.ConditionalUpdates)
518+
risks := risksInOrder(riskVersions)
519+
u.RiskConditions = loadRiskConditions(ctx, risks, riskVersions, u.ConditionRegistry)
520+
453521
if err := sanityCheck(u.ConditionalUpdates); err != nil {
454522
klog.Errorf("Sanity check failed which might impact risk evaluation: %v", err)
455523
}
456524
for i, conditionalUpdate := range u.ConditionalUpdates {
457-
condition := evaluateConditionalUpdate(ctx, conditionalUpdate.Risks, u.ConditionRegistry, u.AcceptRisks, u.ShouldReconcileAcceptRisks, u.RiskConditions)
525+
condition := evaluateConditionalUpdate(conditionalUpdate.Risks, u.AcceptRisks, u.ShouldReconcileAcceptRisks, u.RiskConditions)
458526

459527
if condition.Status == metav1.ConditionTrue {
460528
u.addUpdate(conditionalUpdate.Release)
@@ -556,9 +624,7 @@ func newRecommendedReason(now, want string) string {
556624
}
557625

558626
func evaluateConditionalUpdate(
559-
ctx context.Context,
560627
risks []configv1.ConditionalUpdateRisk,
561-
conditionRegistry clusterconditions.ConditionRegistry,
562628
acceptRisks sets.Set[string],
563629
shouldReconcileAcceptRisks func() bool,
564630
riskConditions map[string][]metav1.Condition,
@@ -573,22 +639,21 @@ func evaluateConditionalUpdate(
573639

574640
var errorMessages []string
575641
for _, risk := range risks {
576-
riskCondition := metav1.Condition{
577-
Type: internal.ConditionalUpdateRiskConditionTypeApplies,
578-
Status: metav1.ConditionFalse,
579-
Reason: riskConditionReasonNotMatch,
642+
riskCondition := meta.FindStatusCondition(riskConditions[risk.Name], internal.ConditionalUpdateRiskConditionTypeApplies)
643+
if riskCondition == nil {
644+
// This should never happen
645+
riskCondition = &metav1.Condition{
646+
Status: metav1.ConditionUnknown,
647+
Reason: internal.ConditionalUpdateRiskConditionReasonInternalErrorFoundNoRiskCondition,
648+
Message: fmt.Sprintf("failed to find risk condition for risk %s", risk.Name),
649+
}
580650
}
581-
if match, err := conditionRegistry.Match(ctx, risk.MatchingRules); err != nil {
582-
msg := unknownExposureMessage(risk, err)
651+
switch riskCondition.Status {
652+
case metav1.ConditionUnknown:
583653
recommended.Status = newRecommendedStatus(recommended.Status, metav1.ConditionUnknown)
584654
recommended.Reason = newRecommendedReason(recommended.Reason, recommendedReasonEvaluationFailed)
585-
errorMessages = append(errorMessages, msg)
586-
riskCondition.Status = metav1.ConditionUnknown
587-
riskCondition.Reason = riskConditionReasonEvaluationFailed
588-
riskCondition.Message = msg
589-
} else if match {
590-
riskCondition.Status = metav1.ConditionTrue
591-
riskCondition.Reason = riskConditionReasonMatch
655+
errorMessages = append(errorMessages, riskCondition.Message)
656+
case metav1.ConditionTrue:
592657
if shouldReconcileAcceptRisks() && acceptRisks.Has(risk.Name) {
593658
recommended.Status = newRecommendedStatus(recommended.Status, metav1.ConditionTrue)
594659
recommended.Reason = newRecommendedReason(recommended.Reason, recommendedReasonAllExposedRisksAccepted)
@@ -604,9 +669,6 @@ func evaluateConditionalUpdate(
604669
errorMessages = append(errorMessages, fmt.Sprintf("%s %s", risk.Message, risk.URL))
605670
}
606671
}
607-
if _, ok := riskConditions[risk.Name]; !ok {
608-
riskConditions[risk.Name] = []metav1.Condition{riskCondition}
609-
}
610672
}
611673
if len(errorMessages) > 0 {
612674
recommended.Message = strings.Join(errorMessages, "\n\n")

0 commit comments

Comments
 (0)