Alerting/Annotations: Prevent panics from composite store jobs from crashing Grafana (#83459)

* Don't directly use pointer to json

* Don't crash entire process if a store job panics

* Add debug logs when failing to parse/handle Loki entries
This commit is contained in:
William Wernert
2024-02-28 20:16:37 +02:00
committed by GitHub
parent 9190fb28e8
commit af528d2f66
7 changed files with 116 additions and 9 deletions
@@ -7,6 +7,7 @@ import (
"testing"
"time"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/annotations"
"github.com/grafana/grafana/pkg/services/annotations/accesscontrol"
"github.com/stretchr/testify/require"
@@ -18,6 +19,22 @@ var (
)
func TestCompositeStore(t *testing.T) {
t.Run("should handle panic", func(t *testing.T) {
r1 := newFakeReader()
getPanic := func(context.Context, *annotations.ItemQuery, *accesscontrol.AccessResources) ([]*annotations.ItemDTO, error) {
panic("ohno")
}
r2 := newFakeReader(withGetFn(getPanic))
store := &CompositeStore{
log.NewNopLogger(),
[]readStore{r1, r2},
}
_, err := store.Get(context.Background(), nil, nil)
require.Error(t, err)
require.Contains(t, err.Error(), "concurrent job panic")
})
t.Run("should return first error", func(t *testing.T) {
err1 := errors.New("error 1")
r1 := newFakeReader(withError(err1))
@@ -25,6 +42,7 @@ func TestCompositeStore(t *testing.T) {
r2 := newFakeReader(withError(err2), withWait(10*time.Millisecond))
store := &CompositeStore{
log.NewNopLogger(),
[]readStore{r1, r2},
}
@@ -64,6 +82,7 @@ func TestCompositeStore(t *testing.T) {
r2 := newFakeReader(withItems(items2))
store := &CompositeStore{
log.NewNopLogger(),
[]readStore{r1, r2},
}
@@ -92,6 +111,7 @@ func TestCompositeStore(t *testing.T) {
r2 := newFakeReader(withTags(tags2))
store := &CompositeStore{
log.NewNopLogger(),
[]readStore{r1, r2},
}
@@ -108,13 +128,23 @@ func TestCompositeStore(t *testing.T) {
}
type fakeReader struct {
items []*annotations.ItemDTO
tagRes annotations.FindTagsResult
wait time.Duration
err error
items []*annotations.ItemDTO
tagRes annotations.FindTagsResult
getFn func(context.Context, *annotations.ItemQuery, *accesscontrol.AccessResources) ([]*annotations.ItemDTO, error)
getTagFn func(context.Context, *annotations.TagsQuery) (annotations.FindTagsResult, error)
wait time.Duration
err error
}
func (f *fakeReader) Type() string {
return "fake"
}
func (f *fakeReader) Get(ctx context.Context, query *annotations.ItemQuery, accessResources *accesscontrol.AccessResources) ([]*annotations.ItemDTO, error) {
if f.getFn != nil {
return f.getFn(ctx, query, accessResources)
}
if f.wait > 0 {
time.Sleep(f.wait)
}
@@ -128,6 +158,10 @@ func (f *fakeReader) Get(ctx context.Context, query *annotations.ItemQuery, acce
}
func (f *fakeReader) GetTags(ctx context.Context, query *annotations.TagsQuery) (annotations.FindTagsResult, error) {
if f.getTagFn != nil {
return f.getTagFn(ctx, query)
}
if f.wait > 0 {
time.Sleep(f.wait)
}
@@ -164,6 +198,12 @@ func withTags(tags []*annotations.TagsDTO) func(*fakeReader) {
}
}
func withGetFn(fn func(context.Context, *annotations.ItemQuery, *accesscontrol.AccessResources) ([]*annotations.ItemDTO, error)) func(*fakeReader) {
return func(f *fakeReader) {
f.getFn = fn
}
}
func newFakeReader(opts ...func(*fakeReader)) *fakeReader {
f := &fakeReader{}
for _, opt := range opts {