diff --git a/pkg/alertmanager/alertspb/compat_test.go b/pkg/alertmanager/alertspb/compat_test.go new file mode 100644 index 00000000000..7a62902a985 --- /dev/null +++ b/pkg/alertmanager/alertspb/compat_test.go @@ -0,0 +1,118 @@ +package alertspb + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestToProto(t *testing.T) { + tests := []struct { + name string + cfg string + templates map[string]string + user string + }{ + { + name: "empty config and no templates", + cfg: "", + templates: nil, + user: "user-1", + }, + { + name: "config with templates", + cfg: "route:\n receiver: default", + templates: map[string]string{ + "slack.tmpl": "{{ define \"slack\" }}alert{{ end }}", + }, + user: "user-2", + }, + { + name: "empty user", + cfg: "global: {}", + templates: nil, + user: "", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := ToProto(tt.cfg, tt.templates, tt.user) + + assert.Equal(t, tt.user, result.User) + assert.Equal(t, tt.cfg, result.RawConfig) + + if tt.templates == nil { + assert.Empty(t, result.Templates) + } else { + assert.Len(t, result.Templates, len(tt.templates)) + for _, tmpl := range result.Templates { + expectedBody, ok := tt.templates[tmpl.Filename] + assert.True(t, ok, "unexpected template filename: %s", tmpl.Filename) + assert.Equal(t, expectedBody, tmpl.Body) + } + } + }) + } +} + +func TestParseTemplates(t *testing.T) { + tests := []struct { + name string + cfg AlertConfigDesc + expected map[string]string + }{ + { + name: "no templates", + cfg: AlertConfigDesc{Templates: nil}, + expected: map[string]string{}, + }, + { + name: "single template", + cfg: AlertConfigDesc{ + Templates: []*TemplateDesc{ + {Filename: "slack.tmpl", Body: "{{ define \"slack\" }}msg{{ end }}"}, + }, + }, + expected: map[string]string{ + "slack.tmpl": "{{ define \"slack\" }}msg{{ end }}", + }, + }, + { + name: "multiple templates", + cfg: AlertConfigDesc{ + Templates: []*TemplateDesc{ + {Filename: "a.tmpl", Body: "body-a"}, + {Filename: "b.tmpl", Body: "body-b"}, + }, + }, + expected: map[string]string{ + "a.tmpl": "body-a", + "b.tmpl": "body-b", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := ParseTemplates(tt.cfg) + assert.Equal(t, tt.expected, result) + }) + } +} + +func TestToProto_ParseTemplates_roundtrip(t *testing.T) { + templates := map[string]string{ + "email.tmpl": "{{ define \"email\" }}hello{{ end }}", + "slack.tmpl": "{{ define \"slack\" }}alert{{ end }}", + } + cfg := "route:\n receiver: default" + user := "test-user" + + proto := ToProto(cfg, templates, user) + result := ParseTemplates(proto) + + assert.Equal(t, templates, result) + assert.Equal(t, cfg, proto.RawConfig) + assert.Equal(t, user, proto.User) +} diff --git a/pkg/compactor/blocks_cleaner.go b/pkg/compactor/blocks_cleaner.go index 4a0e13c2018..cedd3211cb5 100644 --- a/pkg/compactor/blocks_cleaner.go +++ b/pkg/compactor/blocks_cleaner.go @@ -357,9 +357,14 @@ func (c *BlocksCleaner) cleanUpActiveUsers(ctx context.Context, users []string, return nil } errChan := make(chan error, 1) - go visitMarkerManager.HeartBeat(ctx, errChan, c.cleanerVisitMarkerFileUpdateInterval, true) + doneChan := make(chan struct{}) + go func() { + visitMarkerManager.HeartBeat(ctx, errChan, c.cleanerVisitMarkerFileUpdateInterval, true) + close(doneChan) + }() defer func() { errChan <- nil + <-doneChan }() return errors.Wrapf(c.cleanUser(ctx, userLogger, userBucket, userID, firstRun), "failed to delete blocks for user: %s", userID) }) @@ -392,9 +397,14 @@ func (c *BlocksCleaner) cleanDeletedUsers(ctx context.Context, users []string) e return nil } errChan := make(chan error, 1) - go visitMarkerManager.HeartBeat(ctx, errChan, c.cleanerVisitMarkerFileUpdateInterval, true) + doneChan := make(chan struct{}) + go func() { + visitMarkerManager.HeartBeat(ctx, errChan, c.cleanerVisitMarkerFileUpdateInterval, true) + close(doneChan) + }() defer func() { errChan <- nil + <-doneChan }() return errors.Wrapf(c.deleteUserMarkedForDeletion(ctx, userLogger, userBucket, userID), "failed to delete user marked for deletion: %s", userID) }) diff --git a/pkg/cortexpb/histograms.go b/pkg/cortexpb/histograms.go index c4ea2a63715..7e9cf04af74 100644 --- a/pkg/cortexpb/histograms.go +++ b/pkg/cortexpb/histograms.go @@ -95,6 +95,41 @@ func FloatHistogramProtoToFloatHistogram(hp Histogram) *histogram.FloatHistogram } } +// HistogramProtoToFloatHistogram extracts a (normal integer) Histogram from the +// provided proto message and returns it as a FloatHistogram. The caller has to +// make sure that the proto message represents an integer histogram and not a +// float histogram, or it panics. +// Changed from https://github.com/prometheus/prometheus/blob/0ab95536115adfe50af249d36d73674be694ca3f/storage/remote/codec.go#L669-L688 +func HistogramProtoToFloatHistogram(hp Histogram) *histogram.FloatHistogram { + if hp.IsFloatHistogram() { + panic("HistogramProtoToFloatHistogram called with a float histogram") + } + return &histogram.FloatHistogram{ + CounterResetHint: histogram.CounterResetHint(hp.ResetHint), + Schema: hp.Schema, + ZeroThreshold: hp.ZeroThreshold, + ZeroCount: float64(hp.GetZeroCountInt()), + Count: float64(hp.GetCountInt()), + Sum: hp.Sum, + PositiveSpans: spansProtoToSpans(hp.GetPositiveSpans()), + PositiveBuckets: deltasToCounts(hp.GetPositiveDeltas()), + NegativeSpans: spansProtoToSpans(hp.GetNegativeSpans()), + NegativeBuckets: deltasToCounts(hp.GetNegativeDeltas()), + CustomValues: hp.GetCustomValues(), + } +} + +// deltasToCounts converts a slice of deltas to a slice of cumulative float64 counts. +func deltasToCounts(deltas []int64) []float64 { + counts := make([]float64, len(deltas)) + var cur float64 + for i, d := range deltas { + cur += float64(d) + counts[i] = cur + } + return counts +} + // HistogramToHistogramProto converts a (normal integer) Histogram to its protobuf message type. // Changed from https://github.com/prometheus/prometheus/blob/0ab95536115adfe50af249d36d73674be694ca3f/storage/remote/codec.go#L709-L723 func HistogramToHistogramProto(timestamp int64, h *histogram.Histogram) Histogram {