[v9.4.x] fix(alerting): fallback to dashboard to get the full targets PART 3 (#78240)

[v9.4.x] fix(alerting): fallback to dashboard to get the full targets PART3
This commit is contained in:
Jean-Philippe Quéméner
2023-11-16 10:37:35 +01:00
committed by GitHub
parent d5bdc3e90d
commit 974850e81a
4 changed files with 122 additions and 33 deletions
@@ -118,7 +118,11 @@ func (m *migration) makeAlertRule(cond condition, da dashAlert, folderUID string
if err != nil {
return nil, fmt.Errorf("failed to migrate alert rule queries: related dashboard could not be loaded: %w", err)
}
dsTypeMap, err := m.fetchDsTypes(cond.Data)
var uids []string
for _, d := range cond.Data {
uids = append(uids, d.DatasourceUID)
}
dsTypeMap, err := m.fetchDsTypes(uids)
if err != nil {
return nil, fmt.Errorf("failed to migrate alert rule queries: datasources could not be loaded: %w", err)
}
@@ -185,15 +189,15 @@ func (m *migration) makeAlertRule(cond condition, da dashAlert, folderUID string
return ar, nil
}
func (m *migration) fetchDashboard(orgID int64, dashboardUID string) (*dashboards.Dashboard, error) {
func (m *migration) fetchDashboard(orgID int64, dashboardUID string) (*dashboard, error) {
// This is a hack for unit tests in 9.4.
if m.sess == nil {
return &dashboards.Dashboard{}, nil
return &dashboard{}, nil
}
var queryResult *dashboards.Dashboard
var queryResult *dashboard
dashboard := dashboards.Dashboard{OrgID: orgID, UID: dashboardUID}
dashboard := dashboard{OrgId: orgID, Uid: dashboardUID}
has, err := m.sess.Get(&dashboard)
@@ -203,14 +207,14 @@ func (m *migration) fetchDashboard(orgID int64, dashboardUID string) (*dashboard
return nil, dashboards.ErrDashboardNotFound
}
dashboard.SetID(dashboard.ID)
dashboard.SetUID(dashboard.UID)
dashboard.setUid(dashboard.Uid)
queryResult = &dashboard
return queryResult, err
}
type dsType struct {
ID int64 `xorm:"id"`
UID string `xorm:"uid"`
Type string `xorm:"type"`
}
@@ -219,31 +223,54 @@ func (dsType) TableName() string {
return "data_source"
}
func (m *migration) fetchDsTypes(data []alertQuery) (map[string]string, error) {
func (m *migration) fetchDsTypes(uids []string) (map[string]*dsType, error) {
// This is a hack for unit tests in 9.4.
if m.sess == nil {
return map[string]string{}, nil
return map[string]*dsType{}, nil
}
result := make(map[string]string)
for _, q := range data {
result[q.DatasourceUID] = ""
result := make(map[string]*dsType)
for _, uid := range uids {
result[uid] = nil
}
var dsTypes []*dsType
for _, uid := range result {
for uid := range result {
dsTypes = append(dsTypes, &dsType{UID: uid})
}
err := m.sess.Find(&dsTypes)
for _, ds := range dsTypes {
result[ds.UID] = ds.Type
result[ds.UID] = ds
}
return result, err
}
func (m *migration) fetchDsTypesByIDs(ids []int64) (map[int64]*dsType, error) {
// This is a hack for unit tests in 9.4.
if m.sess == nil {
return map[int64]*dsType{}, nil
}
result := make(map[int64]*dsType)
for _, id := range ids {
result[id] = nil
}
var dsTypes []*dsType
for id := range result {
dsTypes = append(dsTypes, &dsType{ID: id})
}
err := m.sess.Find(&dsTypes)
for _, ds := range dsTypes {
result[ds.ID] = ds
}
return result, err
}
// migrateAlertRuleQueries attempts to fix alert rule queries so they can work in unified alerting. Queries of some data sources are not compatible with unified alerting.
func migrateAlertRuleQueries(l log.Logger, ruleID int64, data []alertQuery, panelID int64, dashboard *dashboards.Dashboard, dsTypes map[string]string) ([]alertQuery, error) {
func migrateAlertRuleQueries(l log.Logger, ruleID int64, data []alertQuery, panelID int64, dashboard *dashboard, dsTypes map[string]*dsType) ([]alertQuery, error) {
result := make([]alertQuery, 0, len(data))
for _, d := range data {
// queries that are expression are not relevant, skip them.
@@ -256,7 +283,7 @@ func migrateAlertRuleQueries(l log.Logger, ruleID int64, data []alertQuery, pane
l.Error("datasource not found", "uid", d.DatasourceUID)
return nil, fmt.Errorf("datasource not found")
}
if dsType != datasources.DS_GRAPHITE {
if dsType.Type != datasources.DS_GRAPHITE {
continue
}
var fixedData map[string]json.RawMessage
@@ -8,7 +8,7 @@ import (
"strings"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/datasources"
"github.com/grafana/grafana/pkg/tsdb/graphite"
)
@@ -29,13 +29,14 @@ type panel struct {
}
type target struct {
RefID string `json:"refId"`
Target string `json:"target"`
RefID string `json:"refId"`
Target string `json:"target"`
TargetFull string `json:"targetFull"`
}
// fixGraphiteReferencedSubQueries attempts to fix graphite referenced sub queries, given unified alerting does not support this.
// targetFull of Graphite data source contains the expanded version of field 'target', so let's copy that.
func fixGraphiteReferencedSubQueries(l log.Logger, queryData map[string]json.RawMessage, ruleID, panelID int64, dashboard *dashboards.Dashboard) map[string]json.RawMessage {
func fixGraphiteReferencedSubQueries(l log.Logger, queryData map[string]json.RawMessage, ruleID, panelID int64, dashboard *dashboard) map[string]json.RawMessage {
if !isFixable(l, queryData) {
return queryData
}
@@ -94,7 +95,7 @@ func isFixable(l log.Logger, queryData map[string]json.RawMessage) bool {
return true
}
func unwrapFromDashboard(l log.Logger, queryData map[string]json.RawMessage, panelID int64, dashboard *dashboards.Dashboard) (string, error) {
func unwrapFromDashboard(l log.Logger, queryData map[string]json.RawMessage, panelID int64, dashboard *dashboard) (string, error) {
refIDRaw, ok := queryData["refId"]
if !ok {
return "", fmt.Errorf("query data does not have field 'refId'")
@@ -203,3 +204,48 @@ func logGraphiteMigrationStats(l log.Logger) {
"success_copy", successfulGraphiteMigrationCopy,
"success_dashboard", successfulGraphiteMigrationDashboard)
}
func fixBrokenGraphitePlaceholders(l log.Logger, dash *dashboard, panelID int64, settings *dashAlertSettings, dsTypes map[int64]*dsType) error {
for i := range settings.Conditions {
cond := settings.Conditions[i]
val, present := dsTypes[cond.Query.DatasourceID]
if !present {
return fmt.Errorf("failed to find datasource with id %d", cond.Query.DatasourceID)
}
if val.Type != datasources.DS_GRAPHITE {
return nil
}
var t target
if err := json.Unmarshal(cond.Query.Model, &t); err != nil {
return err
}
if t.Target != "" && !hasPlaceholders(t.Target) {
continue
}
if t.TargetFull != "" && !hasPlaceholders(t.TargetFull) {
continue
}
var data map[string]json.RawMessage
if err := json.Unmarshal(cond.Query.Model, &data); err != nil {
return err
}
unwrapped, err := unwrapFromDashboard(nil, data, panelID, dash)
if err != nil {
return err
}
marshUnwrapped, err := json.Marshal(unwrapped)
if err != nil {
l.Error("error", "err", err)
return err
}
data[graphite.TargetFullModelField] = marshUnwrapped
b, err := json.Marshal(data)
if err != nil {
l.Error("error", "err", err)
return err
}
cond.Query.Model = b
settings.Conditions[i] = cond
}
return nil
}
@@ -9,7 +9,7 @@ import (
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/datasources"
)
func TestMigrateAlertRuleQueries(t *testing.T) {
@@ -18,7 +18,7 @@ func TestMigrateAlertRuleQueries(t *testing.T) {
input *simplejson.Json
expected string
err error
dashboard *dashboards.Dashboard
dashboard *dashboard
}{
{
name: "when a query has a sub query - it is extracted",
@@ -27,7 +27,7 @@ func TestMigrateAlertRuleQueries(t *testing.T) {
"target": "ahalfquery",
}),
expected: `{"target":"thisisafullquery"}`,
dashboard: &dashboards.Dashboard{},
dashboard: &dashboard{},
},
{
name: "when a query has a sub query that is not fully unwrapped, it unwraps it",
@@ -37,7 +37,7 @@ func TestMigrateAlertRuleQueries(t *testing.T) {
"target": "alias(#A, #A)",
}),
expected: `{"refId":"B", "target": "alias(xxx, xxx)"}`,
dashboard: &dashboards.Dashboard{
dashboard: &dashboard{
Data: simplejson.MustJson([]byte(`{"panels":[{"id":0,"targets":[{"refId":"A","target":"xxx"},{"refId":"B","target":"alias(#A, #A)"}]}]}`)),
},
},
@@ -47,7 +47,7 @@ func TestMigrateAlertRuleQueries(t *testing.T) {
"target": "ahalfquery",
}),
expected: `{"target":"ahalfquery"}`,
dashboard: &dashboards.Dashboard{},
dashboard: &dashboard{},
},
{
name: "when query was hidden, it removes the flag",
@@ -55,7 +55,7 @@ func TestMigrateAlertRuleQueries(t *testing.T) {
"hide": true,
}),
expected: `{}`,
dashboard: &dashboards.Dashboard{},
dashboard: &dashboard{},
},
}
@@ -63,7 +63,7 @@ func TestMigrateAlertRuleQueries(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
model, err := tt.input.Encode()
require.NoError(t, err)
queries, err := migrateAlertRuleQueries(log.NewNopLogger(), 0, []alertQuery{{Model: model, DatasourceUID: "a"}}, 0, tt.dashboard, map[string]string{"a": "graphite"})
queries, err := migrateAlertRuleQueries(log.NewNopLogger(), 0, []alertQuery{{Model: model, DatasourceUID: "a"}}, 0, tt.dashboard, map[string]*dsType{"a": {Type: datasources.DS_GRAPHITE}})
if tt.err != nil {
require.Error(t, err)
require.EqualError(t, err, tt.err.Error())
@@ -292,13 +292,8 @@ func (m *migration) Exec(sess *xorm.Session, mg *migrator.Migrator) error {
for _, da := range dashAlerts {
l := mg.Logger.New("ruleID", da.Id, "ruleName", da.Name, "dashboardUID", da.DashboardUID, "orgID", da.OrgId)
newCond, err := transConditions(*da.ParsedSettings, da.OrgId, dsIDMap)
if err != nil {
return err
}
da.DashboardUID = dashIDMap[[2]int64{da.OrgId, da.DashboardId}]
// get dashboard
dash := dashboard{}
exists, err := m.sess.Where("org_id=? AND uid=?", da.OrgId, da.DashboardUID).Get(&dash)
@@ -314,6 +309,27 @@ func (m *migration) Exec(sess *xorm.Session, mg *migrator.Migrator) error {
AlertId: da.Id,
}
}
var ids []int64
for _, d := range da.ParsedSettings.Conditions {
ids = append(ids, d.Query.DatasourceID)
}
dsTypes, err := m.fetchDsTypesByIDs(ids)
if err != nil {
return MigrationError{
Err: err,
AlertId: da.Id,
}
}
// Make sure that targetFull is correctly set for Graphite based alerts before starting the migration
if err := fixBrokenGraphitePlaceholders(l, &dash, da.PanelId, da.ParsedSettings, dsTypes); err != nil {
l.Error("failed to fix broken graphite query", "err", err)
continue
}
newCond, err := transConditions(*da.ParsedSettings, da.OrgId, dsIDMap)
if err != nil {
return err
}
var folder *dashboard
switch {