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 {
if r == nil {
return nil
}
return &definitions.AlertRuleRecordExport{
Metric: r.Metric,
From: r.From,
Metric: r.Metric,
From: r.From,
TargetDatasourceUID: pointerOmitEmpty(r.TargetDatasourceUID),
}
}
@ -509,8 +517,9 @@ func ModelRecordFromApiRecord(r *definitions.Record) *models.Record {
return nil
}
return &models.Record{
Metric: r.Metric,
From: r.From,
Metric: r.Metric,
From: r.From,
TargetDatasourceUID: r.TargetDatasourceUID,
}
}
@ -519,8 +528,9 @@ func ApiRecordFromModelRecord(r *models.Record) *definitions.Record {
return nil
}
return &definitions.Record{
Metric: r.Metric,
From: r.From,
Metric: r.Metric,
From: r.From,
TargetDatasourceUID: r.TargetDatasourceUID,
}
}

View File

@ -533,6 +533,10 @@ type Record struct {
// required: true
// example: A
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

View File

@ -308,6 +308,7 @@ type AlertRuleNotificationSettingsExport struct {
// Record is the provisioned export of models.Record.
type AlertRuleRecordExport struct {
Metric string `json:"metric" yaml:"metric" hcl:"metric"`
From string `json:"from" yaml:"from" hcl:"from"`
Metric string `json:"metric" yaml:"metric" hcl:"metric"`
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
// From contains a query RefID, indicating which expression node is the output of the recording rule.
From string
// TargetDatasourceUID is the data source to write the result of the recording rule.
TargetDatasourceUID string
}
func (r *Record) Fingerprint() data.Fingerprint {
@ -1024,6 +1026,7 @@ func (r *Record) Fingerprint() data.Fingerprint {
writeString(r.Metric)
writeString(r.From)
writeString(r.TargetDatasourceUID)
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()
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)
if err != nil {

View File

@ -50,6 +50,7 @@ type RulesStore interface {
type RecordingWriter interface {
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.

View File

@ -2,6 +2,7 @@ package writer
import (
"context"
"errors"
"time"
"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)
}
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 {
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.
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)