Alerting: Store instance annotations in alert rule state (#114975)

Alerting: Store annotations in alert instance state
This commit is contained in:
Alexander Akhmetov
2025-12-09 13:52:42 +01:00
committed by GitHub
parent 6746c978b4
commit c59d5d1c8e
17 changed files with 258 additions and 109 deletions
@@ -17,7 +17,6 @@ import (
"github.com/grafana/grafana/pkg/services/ngalert/models"
pb "github.com/grafana/grafana/pkg/services/ngalert/store/proto/v1"
"github.com/grafana/grafana/pkg/services/ngalert/tests"
"github.com/grafana/grafana/pkg/util"
)
const baseIntervalSeconds = 10
@@ -51,7 +50,15 @@ func TestIntegration_CompressedAlertRuleStateOperations(t *testing.T) {
name: "can save and read alert rule state",
setupInstances: func() []models.AlertInstance {
return []models.AlertInstance{
createAlertInstance(alertRule1.OrgID, alertRule1.UID, "labelsHash1", string(models.InstanceStateError), models.InstanceStateFiring),
*models.AlertInstanceGen(
models.InstanceMuts.WithOrgID(alertRule1.OrgID),
models.InstanceMuts.WithRuleUID(alertRule1.UID),
models.InstanceMuts.WithLabelsHash("labelsHash1"),
models.InstanceMuts.WithReason(string(models.InstanceStateError)),
models.InstanceMuts.WithState(models.InstanceStateFiring),
models.InstanceMuts.WithLabels(models.InstanceLabels{"label1": "value1"}),
models.InstanceMuts.WithAnnotations(models.InstanceAnnotations{"annotation1": "value1"}),
),
}
},
listQuery: &models.ListAlertInstancesQuery{
@@ -67,8 +74,22 @@ func TestIntegration_CompressedAlertRuleStateOperations(t *testing.T) {
name: "can save and read alert rule state with multiple instances",
setupInstances: func() []models.AlertInstance {
return []models.AlertInstance{
createAlertInstance(alertRule1.OrgID, alertRule1.UID, "hash1", "", models.InstanceStateFiring),
createAlertInstance(alertRule1.OrgID, alertRule1.UID, "hash2", "", models.InstanceStateFiring),
*models.AlertInstanceGen(
models.InstanceMuts.WithOrgID(alertRule1.OrgID),
models.InstanceMuts.WithRuleUID(alertRule1.UID),
models.InstanceMuts.WithLabelsHash("hash1"),
models.InstanceMuts.WithState(models.InstanceStateFiring),
models.InstanceMuts.WithLabels(models.InstanceLabels{"label1": "value1"}),
models.InstanceMuts.WithAnnotations(models.InstanceAnnotations{"annotation1": "value1"}),
),
*models.AlertInstanceGen(
models.InstanceMuts.WithOrgID(alertRule1.OrgID),
models.InstanceMuts.WithRuleUID(alertRule1.UID),
models.InstanceMuts.WithLabelsHash("hash2"),
models.InstanceMuts.WithState(models.InstanceStateFiring),
models.InstanceMuts.WithLabels(models.InstanceLabels{"label1": "value1"}),
models.InstanceMuts.WithAnnotations(models.InstanceAnnotations{"annotation1": "value1"}),
),
}
},
listQuery: &models.ListAlertInstancesQuery{
@@ -109,19 +130,6 @@ func containsHash(t *testing.T, instances []*models.AlertInstance, hash string)
require.Fail(t, fmt.Sprintf("%v does not contain an instance with hash %s", instances, hash))
}
func createAlertInstance(orgID int64, ruleUID, labelsHash, reason string, state models.InstanceStateType) models.AlertInstance {
return models.AlertInstance{
AlertInstanceKey: models.AlertInstanceKey{
RuleOrgID: orgID,
RuleUID: ruleUID,
LabelsHash: labelsHash,
},
CurrentState: state,
CurrentReason: reason,
Labels: models.InstanceLabels{"label1": "value1"},
}
}
func TestIntegrationAlertInstanceOperations(t *testing.T) {
testutil.SkipIntegrationTestInShortMode(t)
@@ -312,7 +320,10 @@ func TestIntegrationFullSync(t *testing.T) {
instances := make([]models.AlertInstance, len(ruleUIDs))
for i, ruleUID := range ruleUIDs {
instances[i] = generateTestAlertInstance(orgID, ruleUID)
instances[i] = *models.AlertInstanceGen(
models.InstanceMuts.WithOrgID(orgID),
models.InstanceMuts.WithRuleUID(ruleUID),
)
}
t.Run("Should do a proper full sync", func(t *testing.T) {
@@ -356,7 +367,7 @@ func TestIntegrationFullSync(t *testing.T) {
t.Run("Should add new entries on sync", func(t *testing.T) {
newRuleUID := "y"
err := ng.InstanceStore.FullSync(ctx, append(instances, generateTestAlertInstance(orgID, newRuleUID)), batchSize, nil)
err := ng.InstanceStore.FullSync(ctx, append(instances, *models.AlertInstanceGen(models.InstanceMuts.WithOrgID(orgID), models.InstanceMuts.WithRuleUID(newRuleUID))), batchSize, nil)
require.NoError(t, err)
res, err := ng.InstanceStore.ListAlertInstances(ctx, &models.ListAlertInstancesQuery{
@@ -381,7 +392,7 @@ func TestIntegrationFullSync(t *testing.T) {
t.Run("Should save all instances when batch size is bigger than 1", func(t *testing.T) {
batchSize = 2
newRuleUID := "y"
err := ng.InstanceStore.FullSync(ctx, append(instances, generateTestAlertInstance(orgID, newRuleUID)), batchSize, nil)
err := ng.InstanceStore.FullSync(ctx, append(instances, *models.AlertInstanceGen(models.InstanceMuts.WithOrgID(orgID), models.InstanceMuts.WithRuleUID(newRuleUID))), batchSize, nil)
require.NoError(t, err)
res, err := ng.InstanceStore.ListAlertInstances(ctx, &models.ListAlertInstancesQuery{
@@ -406,8 +417,8 @@ func TestIntegrationFullSync(t *testing.T) {
t.Run("Should not fail when the instances are empty", func(t *testing.T) {
// First, insert some data into the table.
initialInstances := []models.AlertInstance{
generateTestAlertInstance(orgID, "preexisting-1"),
generateTestAlertInstance(orgID, "preexisting-2"),
*models.AlertInstanceGen(models.InstanceMuts.WithOrgID(orgID), models.InstanceMuts.WithRuleUID("preexisting-1")),
*models.AlertInstanceGen(models.InstanceMuts.WithOrgID(orgID), models.InstanceMuts.WithRuleUID("preexisting-2")),
}
err := ng.InstanceStore.FullSync(ctx, initialInstances, 5, nil)
require.NoError(t, err)
@@ -439,9 +450,9 @@ func TestIntegrationFullSync(t *testing.T) {
t.Run("Should handle invalid instances by skipping them", func(t *testing.T) {
// Create a batch with one valid and one invalid instance
validInstance := generateTestAlertInstance(orgID, "valid")
validInstance := *models.AlertInstanceGen(models.InstanceMuts.WithOrgID(orgID), models.InstanceMuts.WithRuleUID("valid"))
invalidInstance := generateTestAlertInstance(orgID, "")
invalidInstance := *models.AlertInstanceGen(models.InstanceMuts.WithOrgID(orgID), models.InstanceMuts.WithRuleUID(""))
// Make the invalid instance actually invalid
invalidInstance.RuleUID = ""
@@ -460,8 +471,8 @@ func TestIntegrationFullSync(t *testing.T) {
t.Run("Should handle batchSize larger than the number of instances", func(t *testing.T) {
// Insert a small number of instances but use a large batchSize
smallSet := []models.AlertInstance{
generateTestAlertInstance(orgID, "batch-test1"),
generateTestAlertInstance(orgID, "batch-test2"),
*models.AlertInstanceGen(models.InstanceMuts.WithOrgID(orgID), models.InstanceMuts.WithRuleUID("batch-test1")),
*models.AlertInstanceGen(models.InstanceMuts.WithOrgID(orgID), models.InstanceMuts.WithRuleUID("batch-test2")),
}
err := ng.InstanceStore.FullSync(ctx, smallSet, 100, nil)
@@ -493,7 +504,7 @@ func TestIntegrationFullSync(t *testing.T) {
largeCount := 300
largeSet := make([]models.AlertInstance, largeCount)
for i := 0; i < largeCount; i++ {
largeSet[i] = generateTestAlertInstance(orgID, fmt.Sprintf("large-%d", i))
largeSet[i] = *models.AlertInstanceGen(models.InstanceMuts.WithOrgID(orgID), models.InstanceMuts.WithRuleUID(fmt.Sprintf("large-%d", i)))
}
err = ng.InstanceStore.FullSync(ctx, largeSet, 50, nil)
@@ -520,7 +531,10 @@ func TestIntegrationFullSyncWithJitter(t *testing.T) {
instances := make([]models.AlertInstance, len(ruleUIDs))
for i, ruleUID := range ruleUIDs {
instances[i] = generateTestAlertInstance(orgID, ruleUID)
instances[i] = *models.AlertInstanceGen(
models.InstanceMuts.WithOrgID(orgID),
models.InstanceMuts.WithRuleUID(ruleUID),
)
}
// Simple jitter function for testing
@@ -565,7 +579,7 @@ func TestIntegrationFullSyncWithJitter(t *testing.T) {
t.Run("Should handle zero delays (immediate execution)", func(t *testing.T) {
testInstances := make([]models.AlertInstance, 2)
for i := 0; i < 2; i++ {
testInstances[i] = generateTestAlertInstance(orgID, fmt.Sprintf("immediate-%d", i))
testInstances[i] = *models.AlertInstanceGen(models.InstanceMuts.WithOrgID(orgID), models.InstanceMuts.WithRuleUID(fmt.Sprintf("immediate-%d", i)))
}
// Function that returns zero delays
@@ -592,7 +606,7 @@ func TestIntegrationFullSyncWithJitter(t *testing.T) {
t.Run("Should execute jitter delays correctly and save data", func(t *testing.T) {
testInstances := make([]models.AlertInstance, 4)
for i := 0; i < 4; i++ {
testInstances[i] = generateTestAlertInstance(orgID, fmt.Sprintf("jitter-test-%d", i))
testInstances[i] = *models.AlertInstanceGen(models.InstanceMuts.WithOrgID(orgID), models.InstanceMuts.WithRuleUID(fmt.Sprintf("jitter-test-%d", i)))
}
// Track jitter function calls
@@ -652,11 +666,16 @@ func TestIntegration_ProtoInstanceDBStore_VerifyCompressedData(t *testing.T) {
alertRule := tests.CreateTestAlertRule(t, ctx, dbstore, 60, 1)
labelsHash := "hash1"
reason := "reason"
state := models.InstanceStateFiring
instances := []models.AlertInstance{
createAlertInstance(alertRule.OrgID, alertRule.UID, labelsHash, reason, state),
*models.AlertInstanceGen(
models.InstanceMuts.WithOrgID(alertRule.OrgID),
models.InstanceMuts.WithRuleUID(alertRule.UID),
models.InstanceMuts.WithLabelsHash("hash1"),
models.InstanceMuts.WithReason("reason"),
models.InstanceMuts.WithState(models.InstanceStateFiring),
models.InstanceMuts.WithLabels(models.InstanceLabels{"label1": "value1"}),
models.InstanceMuts.WithAnnotations(models.InstanceAnnotations{"annotation1": "value1"}),
),
}
err := ng.InstanceStore.SaveAlertInstancesForRule(ctx, alertRule.GetKeyWithGroup(), instances)
@@ -704,25 +723,3 @@ func decompressAlertInstances(compressed []byte) ([]*pb.AlertInstance, error) {
return instances.Instances, nil
}
func generateTestAlertInstance(orgID int64, ruleID string) models.AlertInstance {
return models.AlertInstance{
AlertInstanceKey: models.AlertInstanceKey{
RuleOrgID: orgID,
RuleUID: ruleID,
LabelsHash: "abc",
},
CurrentState: models.InstanceStateFiring,
Labels: map[string]string{
"hello": "world",
},
ResultFingerprint: "abc",
CurrentStateEnd: time.Now(),
CurrentStateSince: time.Now(),
LastEvalTime: time.Now(),
LastSentAt: util.Pointer(time.Now()),
FiredAt: util.Pointer(time.Now()),
ResolvedAt: util.Pointer(time.Now()),
CurrentReason: "abc",
}
}