[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:
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user