Alerting: Attempt to retry retryable errors (#79037)

* Alerting: Attempt to retry retryable errors

Currently in a draft state, but this was the minimal diff I could put together to exemplify how could achieve this.

Signed-off-by: gotjosh <josue.abreu@gmail.com>

---------

Signed-off-by: gotjosh <josue.abreu@gmail.com>
This commit is contained in:
gotjosh
2023-12-06 16:35:22 +00:00
committed by GitHub
parent 7e331c8507
commit 3e51cf0949
4 changed files with 156 additions and 43 deletions
+29
View File
@@ -157,6 +157,23 @@ func (evalResults Results) HasErrors() bool {
return false
}
// HasNonRetryableErrors returns true if we have at least 1 result with:
// 1. A `State` of `Error`
// 2. The `Error` attribute is not nil
// 3. The `Error` type is of `&invalidEvalResultFormatError`
// Our thinking with this approach, is that we don't want to retry errors that have relation with invalid alert definition format.
func (evalResults Results) HasNonRetryableErrors() bool {
for _, r := range evalResults {
if r.State == Error && r.Error != nil {
var nonRetryableError *invalidEvalResultFormatError
if errors.As(r.Error, &nonRetryableError) {
return true
}
}
}
return false
}
// HasErrors returns true when Results contains at least one element and all elements are errors
func (evalResults Results) IsError() bool {
for _, r := range evalResults {
@@ -177,6 +194,18 @@ func (evalResults Results) IsNoData() bool {
return true
}
// Error returns the aggregated `error` of all results of which state is `Error`.
func (evalResults Results) Error() error {
var errs []error
for _, result := range evalResults {
if result.State == Error && result.Error != nil {
errs = append(errs, result.Error)
}
}
return errors.Join(errs...)
}
// Result contains the evaluated State of an alert instance
// identified by its labels.
type Result struct {
+65
View File
@@ -2,6 +2,7 @@ package eval
import (
"context"
"errors"
"fmt"
"math/rand"
"testing"
@@ -769,6 +770,70 @@ func TestEvaluateRaw(t *testing.T) {
})
}
func TestResults_HasNonRetryableErrors(t *testing.T) {
tc := []struct {
name string
eval Results
expected bool
}{
{
name: "with non-retryable errors",
eval: Results{
{
State: Error,
Error: &invalidEvalResultFormatError{refID: "A", reason: "unable to get frame row length", err: errors.New("weird error")},
},
},
expected: true,
},
{
name: "with retryable errors",
eval: Results{
{
State: Error,
Error: errors.New("some weird error"),
},
},
expected: false,
},
}
for _, tt := range tc {
t.Run(tt.name, func(t *testing.T) {
require.Equal(t, tt.expected, tt.eval.HasNonRetryableErrors())
})
}
}
func TestResults_Error(t *testing.T) {
tc := []struct {
name string
eval Results
expected string
}{
{
name: "with non-retryable errors",
eval: Results{
{
State: Error,
Error: &invalidEvalResultFormatError{refID: "A", reason: "unable to get frame row length", err: errors.New("weird error")},
},
{
State: Error,
Error: errors.New("unable to get a data frame"),
},
},
expected: "invalid format of evaluation results for the alert definition A: unable to get frame row length: weird error\nunable to get a data frame",
},
}
for _, tt := range tc {
t.Run(tt.name, func(t *testing.T) {
require.Equal(t, tt.expected, tt.eval.Error().Error())
})
}
}
type fakeExpressionService struct {
hook func(ctx context.Context, now time.Time, pipeline expr.DataPipeline) (*backend.QueryDataResponse, error)
}