Alerting: Fix Conflicting Alert Rule Response Has Wrong 'rule_uid' (#95013)

* change to return the right conflicting alert rule uid. see https://github.com/grafana/grafana/issues/89755

Signed-off-by: wasim-nihal <sswasim64@gmail.com>

* correcting the code comment

Signed-off-by: wasim-nihal <sswasim64@gmail.com>

* changes to return the conflicting uid for both insert and update operations

Signed-off-by: wasim-nihal <sswasim64@gmail.com>

* changes to return verbose conflicting alert rule response payload

Signed-off-by: wasim-nihal <sswasim64@gmail.com>

* changes to return verbose conflicting alert rule response payload

Signed-off-by: wasim-nihal <sswasim64@gmail.com>

* Update pkg/services/ngalert/store/alert_rule.go

Co-authored-by: Matthew Jacobson <JacobsonMT@gmail.com>

---------

Signed-off-by: wasim-nihal <sswasim64@gmail.com>
Co-authored-by: Matthew Jacobson <JacobsonMT@gmail.com>
This commit is contained in:
Nihal
2024-11-26 15:13:31 -05:00
committed by GitHub
co-authored by Matthew Jacobson
parent 52f6f69d4b
commit e73bb34cc0
2 changed files with 41 additions and 7 deletions
+21 -2
View File
@@ -7,9 +7,12 @@ import (
)
var (
errAlertRuleConflictMsg = "conflicting alert rule found [rule_uid: '{{ .Public.RuleUID }}', title: '{{ .Public.Title }}', namespace_uid: '{{ .Public.NamespaceUID }}']: {{ .Public.Error }}"
ErrAlertRuleConflictBase = errutil.Conflict("alerting.alert-rule.conflict").
errAlertRuleConflictMsg = "conflicting alert rule found [rule_uid: '{{ .Public.RuleUID }}', title: '{{ .Public.Title }}', namespace_uid: '{{ .Public.NamespaceUID }}']: {{ .Public.Error }}"
errAlertRuleConflictMsgVerbose = "alert rule [rule_uid: '{{ .Public.New.RuleUID }}', title: '{{ .Public.New.Title }}', namespace_uid: '{{ .Public.New.NamespaceUID }}'] conflicts with existing [rule_uid: '{{ .Public.Existing.RuleUID }}', title: '{{ .Public.Existing.Title }}', namespace_uid: '{{ .Public.Existing.NamespaceUID }}']: {{ .Public.Error }}"
ErrAlertRuleConflictBase = errutil.Conflict("alerting.alert-rule.conflict").
MustTemplate(errAlertRuleConflictMsg, errutil.WithPublic(errAlertRuleConflictMsg))
ErrAlertRuleConflictBaseVerbose = errutil.Conflict("alerting.alert-rule.conflict").
MustTemplate(errAlertRuleConflictMsgVerbose, errutil.WithPublic(errAlertRuleConflictMsgVerbose))
ErrAlertRuleGroupNotFound = errutil.NotFound("alerting.alert-rule.notFound")
ErrInvalidRelativeTimeRangeBase = errutil.BadRequest("alerting.alert-rule.invalidRelativeTime").MustTemplate("Invalid alert rule query {{ .Public.RefID }}: invalid relative time range [From: {{ .Public.From }}, To: {{ .Public.To }}]")
ErrConditionNotExistBase = errutil.BadRequest("alerting.alert-rule.conditionNotExist").MustTemplate("Condition {{ .Public.Given }} does not exist, must be one of {{ .Public.Existing }}")
@@ -19,6 +22,22 @@ func ErrAlertRuleConflict(rule AlertRule, underlying error) error {
return ErrAlertRuleConflictBase.Build(errutil.TemplateData{Public: map[string]any{"RuleUID": rule.UID, "Title": rule.Title, "NamespaceUID": rule.NamespaceUID, "Error": underlying.Error()}, Error: underlying})
}
func ErrAlertRuleConflictVerbose(existingPartialRule, rule AlertRule, underlying error) error {
return ErrAlertRuleConflictBaseVerbose.Build(errutil.TemplateData{Public: map[string]any{
"New": map[string]any{
"RuleUID": rule.UID,
"Title": rule.Title,
"NamespaceUID": rule.NamespaceUID,
},
"Existing": map[string]any{
"RuleUID": existingPartialRule.UID,
"Title": existingPartialRule.Title,
"NamespaceUID": existingPartialRule.NamespaceUID,
},
"Error": underlying.Error(),
}, Error: underlying})
}
func ErrInvalidRelativeTimeRange(refID string, rtr RelativeTimeRange) error {
return ErrInvalidRelativeTimeRangeBase.Build(errutil.TemplateData{Public: map[string]any{"RefID": refID, "From": rtr.From, "To": rtr.To}})
}
+20 -5
View File
@@ -15,6 +15,7 @@ import (
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/featuremgmt"
@@ -216,7 +217,7 @@ func (st DBstore) InsertAlertRules(ctx context.Context, rules []ngmodels.AlertRu
for i := range newRules {
if _, err := sess.Insert(&newRules[i]); err != nil {
if st.SQLStore.GetDialect().IsUniqueConstraintViolation(err) {
return ruleConstraintViolationToErr(rules[i], err)
return ruleConstraintViolationToErr(sess, rules[i], err, st.Logger)
}
return fmt.Errorf("failed to create new rules: %w", err)
}
@@ -273,7 +274,7 @@ func (st DBstore) UpdateAlertRules(ctx context.Context, rules []ngmodels.UpdateR
if updated, err := sess.ID(r.Existing.ID).AllCols().Update(converted); err != nil || updated == 0 {
if err != nil {
if st.SQLStore.GetDialect().IsUniqueConstraintViolation(err) {
return ruleConstraintViolationToErr(r.New, err)
return ruleConstraintViolationToErr(sess, r.New, err, st.Logger)
}
return fmt.Errorf("failed to update rule [%s] %s: %w", r.New.UID, r.New.Title, err)
}
@@ -1033,12 +1034,26 @@ func (st DBstore) RenameTimeIntervalInNotificationSettings(
return result, nil, st.UpdateAlertRules(ctx, updates)
}
func ruleConstraintViolationToErr(rule ngmodels.AlertRule, err error) error {
func ruleConstraintViolationToErr(sess *db.Session, rule ngmodels.AlertRule, err error, logger log.Logger) error {
msg := err.Error()
if strings.Contains(msg, "UQE_alert_rule_org_id_namespace_uid_title") || strings.Contains(msg, "alert_rule.org_id, alert_rule.namespace_uid, alert_rule.title") {
return ngmodels.ErrAlertRuleConflict(rule, ngmodels.ErrAlertRuleUniqueConstraintViolation)
// return verbose conflicting alert rule error response
// see: https://github.com/grafana/grafana/issues/89755
var fetched_uid string
var existingPartialAlertRule ngmodels.AlertRule
ok, uid_fetch_err := sess.Table("alert_rule").Cols("uid").Where("org_id = ? AND title = ? AND namespace_uid = ?", rule.OrgID, rule.Title, rule.NamespaceUID).Get(&fetched_uid)
if uid_fetch_err != nil {
logger.Error("Error fetching uid from alert_rule table", "reason", uid_fetch_err.Error())
}
if ok {
existingPartialAlertRule = ngmodels.AlertRule{UID: fetched_uid, Title: rule.Title, NamespaceUID: rule.NamespaceUID}
}
return ngmodels.ErrAlertRuleConflictVerbose(existingPartialAlertRule, rule, ngmodels.ErrAlertRuleUniqueConstraintViolation)
} else if strings.Contains(msg, "UQE_alert_rule_org_id_uid") || strings.Contains(msg, "alert_rule.org_id, alert_rule.uid") {
return ngmodels.ErrAlertRuleConflict(rule, errors.New("rule UID under the same organisation should be unique"))
// return verbose conflicting alert rule error response
// see: https://github.com/grafana/grafana/issues/89755
existingPartialAlertRule := ngmodels.AlertRule{UID: rule.UID}
return ngmodels.ErrAlertRuleConflictVerbose(existingPartialAlertRule, rule, errors.New("rule UID under the same organisation should be unique"))
} else {
return ngmodels.ErrAlertRuleConflict(rule, err)
}