Alerting: Extend recording rule definitions/interfaces with data source. (#101678)

Extend the recording rule definition to include the target data source, allowing
configuration of where the output of the recording rule is written to. Also
extends the relevant interfaces in preparation for the next set of changes.
This commit is contained in:
Steve Simpson
2025-03-06 14:09:17 +01:00
committed by GitHub
parent 13cf67de53
commit b7dcfcedcb
9 changed files with 57 additions and 9 deletions

View File

@ -494,13 +494,21 @@ func NotificationSettingsFromAlertRuleNotificationSettings(ns *definitions.Alert
} }
} }
func pointerOmitEmpty(s string) *string {
if s == "" {
return nil
}
return &s
}
func AlertRuleRecordExportFromRecord(r *models.Record) *definitions.AlertRuleRecordExport { func AlertRuleRecordExportFromRecord(r *models.Record) *definitions.AlertRuleRecordExport {
if r == nil { if r == nil {
return nil return nil
} }
return &definitions.AlertRuleRecordExport{ return &definitions.AlertRuleRecordExport{
Metric: r.Metric, Metric: r.Metric,
From: r.From, From: r.From,
TargetDatasourceUID: pointerOmitEmpty(r.TargetDatasourceUID),
} }
} }
@ -509,8 +517,9 @@ func ModelRecordFromApiRecord(r *definitions.Record) *models.Record {
return nil return nil
} }
return &models.Record{ return &models.Record{
Metric: r.Metric, Metric: r.Metric,
From: r.From, From: r.From,
TargetDatasourceUID: r.TargetDatasourceUID,
} }
} }
@ -519,8 +528,9 @@ func ApiRecordFromModelRecord(r *models.Record) *definitions.Record {
return nil return nil
} }
return &definitions.Record{ return &definitions.Record{
Metric: r.Metric, Metric: r.Metric,
From: r.From, From: r.From,
TargetDatasourceUID: r.TargetDatasourceUID,
} }
} }

View File

@ -533,6 +533,10 @@ type Record struct {
// required: true // required: true
// example: A // example: A
From string `json:"from" yaml:"from"` From string `json:"from" yaml:"from"`
// Which data source should be used to write the output of the recording rule, specified by UID.
// required: false
// example: my-prom
TargetDatasourceUID string `json:"target_datasource_uid,omitempty" yaml:"target_datasource_uid,omitempty"`
} }
// swagger:model // swagger:model

View File

@ -308,6 +308,7 @@ type AlertRuleNotificationSettingsExport struct {
// Record is the provisioned export of models.Record. // Record is the provisioned export of models.Record.
type AlertRuleRecordExport struct { type AlertRuleRecordExport struct {
Metric string `json:"metric" yaml:"metric" hcl:"metric"` Metric string `json:"metric" yaml:"metric" hcl:"metric"`
From string `json:"from" yaml:"from" hcl:"from"` From string `json:"from" yaml:"from" hcl:"from"`
TargetDatasourceUID *string `json:"targetDatasourceUid,omitempty" yaml:"targetDatasourceUid,omitempty" hcl:"target_datasource_uid,optional"`
} }

View File

@ -1010,6 +1010,8 @@ type Record struct {
Metric string Metric string
// From contains a query RefID, indicating which expression node is the output of the recording rule. // From contains a query RefID, indicating which expression node is the output of the recording rule.
From string From string
// TargetDatasourceUID is the data source to write the result of the recording rule.
TargetDatasourceUID string
} }
func (r *Record) Fingerprint() data.Fingerprint { func (r *Record) Fingerprint() data.Fingerprint {
@ -1024,6 +1026,7 @@ func (r *Record) Fingerprint() data.Fingerprint {
writeString(r.Metric) writeString(r.Metric)
writeString(r.From) writeString(r.From)
writeString(r.TargetDatasourceUID)
return data.Fingerprint(h.Sum64()) return data.Fingerprint(h.Sum64())
} }

View File

@ -269,7 +269,7 @@ func (r *recordingRule) tryEvaluation(ctx context.Context, ev *Evaluation, logge
} }
writeStart := r.clock.Now() writeStart := r.clock.Now()
err = r.writer.Write(ctx, ev.rule.Record.Metric, ev.scheduledAt, frames, ev.rule.OrgID, ev.rule.Labels) err = r.writer.WriteDatasource(ctx, ev.rule.Record.TargetDatasourceUID, ev.rule.Record.Metric, ev.scheduledAt, frames, ev.rule.OrgID, ev.rule.Labels)
writeDur := r.clock.Now().Sub(writeStart) writeDur := r.clock.Now().Sub(writeStart)
if err != nil { if err != nil {

View File

@ -50,6 +50,7 @@ type RulesStore interface {
type RecordingWriter interface { type RecordingWriter interface {
Write(ctx context.Context, name string, t time.Time, frames data.Frames, orgID int64, extraLabels map[string]string) error Write(ctx context.Context, name string, t time.Time, frames data.Frames, orgID int64, extraLabels map[string]string) error
WriteDatasource(ctx context.Context, dsUID string, name string, t time.Time, frames data.Frames, orgID int64, extraLabels map[string]string) error
} }
// AlertRuleStopReasonProvider is an interface for determining the reason why an alert rule was stopped. // AlertRuleStopReasonProvider is an interface for determining the reason why an alert rule was stopped.

View File

@ -2,6 +2,7 @@ package writer
import ( import (
"context" "context"
"errors"
"time" "time"
"github.com/grafana/grafana-plugin-sdk-go/data" "github.com/grafana/grafana-plugin-sdk-go/data"
@ -18,3 +19,15 @@ func (w FakeWriter) Write(ctx context.Context, name string, t time.Time, frames
return w.WriteFunc(ctx, name, t, frames, orgID, extraLabels) return w.WriteFunc(ctx, name, t, frames, orgID, extraLabels)
} }
func (w FakeWriter) WriteDatasource(ctx context.Context, dsUID string, name string, t time.Time, frames data.Frames, orgID int64, extraLabels map[string]string) error {
if w.WriteFunc == nil {
return nil
}
if dsUID != "" {
return errors.New("expected empty data source uid")
}
return w.WriteFunc(ctx, name, t, frames, orgID, extraLabels)
}

View File

@ -12,3 +12,7 @@ type NoopWriter struct{}
func (w NoopWriter) Write(ctx context.Context, name string, t time.Time, frames data.Frames, orgID int64, extraLabels map[string]string) error { func (w NoopWriter) Write(ctx context.Context, name string, t time.Time, frames data.Frames, orgID int64, extraLabels map[string]string) error {
return nil return nil
} }
func (w NoopWriter) WriteDatasource(ctx context.Context, dsUID string, name string, t time.Time, frames data.Frames, orgID int64, extraLabels map[string]string) error {
return nil
}

View File

@ -192,6 +192,18 @@ func createAuthOpts(username, password string) *httpclient.BasicAuthOptions {
} }
} }
// Write writes the given frames to the Prometheus remote write endpoint.
func (w PrometheusWriter) WriteDatasource(ctx context.Context, dsUID string, name string, t time.Time, frames data.Frames, orgID int64, extraLabels map[string]string) error {
l := w.logger.FromContext(ctx)
if dsUID != "" {
l.Error("Writing to specific data sources is not enabled", "org_id", orgID, "datasource_uid", dsUID)
return errors.New("writing to specific data sources is not enabled")
}
return w.Write(ctx, name, t, frames, orgID, extraLabels)
}
// Write writes the given frames to the Prometheus remote write endpoint. // Write writes the given frames to the Prometheus remote write endpoint.
func (w PrometheusWriter) Write(ctx context.Context, name string, t time.Time, frames data.Frames, orgID int64, extraLabels map[string]string) error { func (w PrometheusWriter) Write(ctx context.Context, name string, t time.Time, frames data.Frames, orgID int64, extraLabels map[string]string) error {
l := w.logger.FromContext(ctx) l := w.logger.FromContext(ctx)