Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(stages): set healthy status for promotions w/o checks #2951

Merged
merged 1 commit into from
Nov 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 20 additions & 20 deletions internal/controller/stages/regular_stages.go
Original file line number Diff line number Diff line change
Expand Up @@ -694,23 +694,27 @@ func (r *RegularStageReconciler) assessHealth(ctx context.Context, stage *kargoa
return newStatus
}

// If there are no health checks to perform, then we can skip the health
// assessment.
healthChecks := lastPromo.Status.HealthChecks
if len(healthChecks) == 0 {
logger.Debug("Stage has no health checks to perform for last Promotion")
// If the last Promotion did not succeed, then we cannot perform any health
// checks because they are only available after a successful Promotion.
//
// TODO(hidde): Long term, this should probably be changed to allow to
// continue to run health checks from the last successful Promotion,
// even if the current Promotion did not succeed (e.g. because it was
// aborted).
Comment on lines +697 to +703
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We discussed offline and agreed that this is the right thing to do in the near-term and longer-term it would be nice to try to run whatever health checks were specified by the last successful promotion. This would likely involve copying health checks over to the Freight reference at the top of the Freight history stack.

This LGTM for now.

if lastPromo.Status.Phase != kargoapi.PromotionPhaseSucceeded {
logger.Debug("Last promotion did not succeed: no health checks to perform")
conditions.Set(&newStatus, &metav1.Condition{
Type: kargoapi.ConditionTypeHealthy,
Status: metav1.ConditionUnknown,
Reason: "NoHealthChecks",
Message: "Stage has no health checks to perform",
Reason: fmt.Sprintf("LastPromotion%s", lastPromo.Status.Phase),
Message: "No health checks to perform for unsuccessful Promotion",
ObservedGeneration: stage.Generation,
})
newStatus.Health = nil
return newStatus
}

// Compose the health check steps.
healthChecks := lastPromo.Status.HealthChecks
var steps []directives.HealthCheckStep
for _, step := range healthChecks {
steps = append(steps, directives.HealthCheckStep{
Expand All @@ -733,22 +737,18 @@ func (r *RegularStageReconciler) assessHealth(ctx context.Context, stage *kargoa
Type: kargoapi.ConditionTypeHealthy,
Status: metav1.ConditionTrue,
Reason: string(health.Status),
Message: "Stage is healthy",
Message: fmt.Sprintf("Stage is healthy (performed %d health checks)", len(healthChecks)),
ObservedGeneration: stage.Generation,
})
case kargoapi.HealthStateUnhealthy:
conditions.Set(&newStatus, &metav1.Condition{
Type: kargoapi.ConditionTypeHealthy,
Status: metav1.ConditionFalse,
Reason: string(health.Status),
Message: "Stage is unhealthy",
ObservedGeneration: stage.Generation,
})
case kargoapi.HealthStateNotApplicable:
conditions.Set(&newStatus, &metav1.Condition{
Type: kargoapi.ConditionTypeHealthy,
Status: metav1.ConditionUnknown,
Reason: string(health.Status),
Type: kargoapi.ConditionTypeHealthy,
Status: metav1.ConditionFalse,
Reason: string(health.Status),
Message: fmt.Sprintf(
"Stage is unhealthy (%d issues in %d health checks)",
len(health.Issues), len(healthChecks),
),
ObservedGeneration: stage.Generation,
})
default:
Expand Down
50 changes: 44 additions & 6 deletions internal/controller/stages/regular_stages_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1204,7 +1204,7 @@ func TestRegularStageReconciler_assessHealth(t *testing.T) {
},
},
{
name: "no health checks",
name: "unsuccessful last promotion",
stage: &kargoapi.Stage{
ObjectMeta: metav1.ObjectMeta{
Namespace: "fake-project",
Expand All @@ -1213,6 +1213,7 @@ func TestRegularStageReconciler_assessHealth(t *testing.T) {
Status: kargoapi.StageStatus{
LastPromotion: &kargoapi.PromotionReference{
Status: &kargoapi.PromotionStatus{
Phase: kargoapi.PromotionPhaseAborted,
HealthChecks: nil,
},
},
Expand All @@ -1224,8 +1225,35 @@ func TestRegularStageReconciler_assessHealth(t *testing.T) {
healthyCond := conditions.Get(&status, kargoapi.ConditionTypeHealthy)
require.NotNil(t, healthyCond)
assert.Equal(t, metav1.ConditionUnknown, healthyCond.Status)
assert.Equal(t, "NoHealthChecks", healthyCond.Reason)
assert.Equal(t, "Stage has no health checks to perform", healthyCond.Message)
assert.Equal(t, "LastPromotionAborted", healthyCond.Reason)
assert.Equal(t, "No health checks to perform for unsuccessful Promotion", healthyCond.Message)
},
},
{
name: "no health checks",
stage: &kargoapi.Stage{
ObjectMeta: metav1.ObjectMeta{
Namespace: "fake-project",
Name: "test-stage",
},
Status: kargoapi.StageStatus{
LastPromotion: &kargoapi.PromotionReference{
Status: &kargoapi.PromotionStatus{
Phase: kargoapi.PromotionPhaseSucceeded,
HealthChecks: nil,
},
},
},
},
assertions: func(t *testing.T, status kargoapi.StageStatus) {
assert.NotNil(t, status.Health)
assert.Equal(t, kargoapi.HealthStateHealthy, status.Health.Status)

healthyCond := conditions.Get(&status, kargoapi.ConditionTypeHealthy)
require.NotNil(t, healthyCond)
assert.Equal(t, metav1.ConditionTrue, healthyCond.Status)
assert.Equal(t, kargoapi.ConditionTypeHealthy, healthyCond.Reason)
assert.Contains(t, healthyCond.Message, "Stage is healthy")
},
},
{
Expand All @@ -1238,6 +1266,7 @@ func TestRegularStageReconciler_assessHealth(t *testing.T) {
Status: kargoapi.StageStatus{
LastPromotion: &kargoapi.PromotionReference{
Status: &kargoapi.PromotionStatus{
Phase: kargoapi.PromotionPhaseSucceeded,
HealthChecks: []kargoapi.HealthCheckStep{
{
Uses: "test-check",
Expand All @@ -1262,7 +1291,7 @@ func TestRegularStageReconciler_assessHealth(t *testing.T) {
require.NotNil(t, healthyCond)
assert.Equal(t, metav1.ConditionTrue, healthyCond.Status)
assert.Equal(t, string(kargoapi.HealthStateHealthy), healthyCond.Reason)
assert.Equal(t, "Stage is healthy", healthyCond.Message)
assert.Contains(t, healthyCond.Message, "Stage is healthy")
},
},
{
Expand All @@ -1275,6 +1304,7 @@ func TestRegularStageReconciler_assessHealth(t *testing.T) {
Status: kargoapi.StageStatus{
LastPromotion: &kargoapi.PromotionReference{
Status: &kargoapi.PromotionStatus{
Phase: kargoapi.PromotionPhaseSucceeded,
HealthChecks: []kargoapi.HealthCheckStep{
{
Uses: "test-check",
Expand All @@ -1289,7 +1319,12 @@ func TestRegularStageReconciler_assessHealth(t *testing.T) {
directives.HealthCheckContext,
[]directives.HealthCheckStep,
) kargoapi.Health {
return kargoapi.Health{Status: kargoapi.HealthStateUnhealthy}
return kargoapi.Health{
Status: kargoapi.HealthStateUnhealthy,
Issues: []string{
"issue-1", "issue-2",
},
}
},
assertions: func(t *testing.T, status kargoapi.StageStatus) {
require.NotNil(t, status.Health)
Expand All @@ -1299,7 +1334,8 @@ func TestRegularStageReconciler_assessHealth(t *testing.T) {
require.NotNil(t, healthyCond)
assert.Equal(t, metav1.ConditionFalse, healthyCond.Status)
assert.Equal(t, string(kargoapi.HealthStateUnhealthy), healthyCond.Reason)
assert.Equal(t, "Stage is unhealthy", healthyCond.Message)
assert.Contains(t, healthyCond.Message, "Stage is unhealthy")
assert.Contains(t, healthyCond.Message, "2 issues in 1 health check")
},
},
{
Expand All @@ -1313,6 +1349,7 @@ func TestRegularStageReconciler_assessHealth(t *testing.T) {
Conditions: []metav1.Condition{},
LastPromotion: &kargoapi.PromotionReference{
Status: &kargoapi.PromotionStatus{
Phase: kargoapi.PromotionPhaseSucceeded,
HealthChecks: []kargoapi.HealthCheckStep{
{
Uses: "test-check",
Expand Down Expand Up @@ -1348,6 +1385,7 @@ func TestRegularStageReconciler_assessHealth(t *testing.T) {
Status: kargoapi.StageStatus{
LastPromotion: &kargoapi.PromotionReference{
Status: &kargoapi.PromotionStatus{
Phase: kargoapi.PromotionPhaseSucceeded,
HealthChecks: []kargoapi.HealthCheckStep{
{
Uses: "test-check",
Expand Down