mirror of
https://github.com/prymitive/karma
synced 2026-05-05 03:16:51 +00:00
feat(api): deduplicate silences
Detect if all alerts are silenced with the same silence ID and expose this information in the API response.
This commit is contained in:
@@ -38,6 +38,7 @@ type LabelsColorMap map[string]map[string]LabelColors
|
||||
type APIAlertGroupSharedMaps struct {
|
||||
Annotations Annotations `json:"annotations"`
|
||||
Labels map[string]string `json:"labels"`
|
||||
Silences map[string]string `json:"silences"`
|
||||
}
|
||||
|
||||
// APIAlertGroup is how AlertGroup is returned in the API response
|
||||
@@ -138,6 +139,46 @@ func (ag *APIAlertGroup) dedupAnnotations() {
|
||||
ag.Shared.Annotations = sharedAnnotations
|
||||
}
|
||||
|
||||
func (ag *APIAlertGroup) dedupSilences() {
|
||||
ag.Shared.Silences = map[string]string{}
|
||||
|
||||
silencesByCluster := map[string][]string{}
|
||||
|
||||
for _, alert := range ag.Alerts {
|
||||
if alert.State != AlertStateSuppressed {
|
||||
// if we find any alert that's not silenced then we can break early
|
||||
return
|
||||
}
|
||||
for _, am := range alert.Alertmanager {
|
||||
for _, silenceID := range am.SilencedBy {
|
||||
_, ok := silencesByCluster[am.Cluster]
|
||||
if !ok {
|
||||
silencesByCluster[am.Cluster] = []string{}
|
||||
}
|
||||
if !slices.StringInSlice(silencesByCluster[am.Cluster], silenceID) {
|
||||
silencesByCluster[am.Cluster] = append(silencesByCluster[am.Cluster], silenceID)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// only deduplicate if all alerts are silenced with the same silence from a
|
||||
// single cluster
|
||||
if len(silencesByCluster) != 1 {
|
||||
return
|
||||
}
|
||||
|
||||
// now check that all alerts are silenced with the same silenceID
|
||||
for cluster, silences := range silencesByCluster {
|
||||
for _, silenceID := range silences {
|
||||
if silenceID != silences[0] {
|
||||
return
|
||||
}
|
||||
}
|
||||
ag.Shared.Silences[cluster] = silences[0]
|
||||
}
|
||||
}
|
||||
|
||||
// DedupSharedMaps will find all labels and annotations shared by all alerts
|
||||
// in this group and moved them to Shared namespace
|
||||
func (ag *APIAlertGroup) DedupSharedMaps() {
|
||||
@@ -147,10 +188,12 @@ func (ag *APIAlertGroup) DedupSharedMaps() {
|
||||
if len(ag.Alerts) > 1 {
|
||||
ag.dedupLabels()
|
||||
ag.dedupAnnotations()
|
||||
ag.dedupSilences()
|
||||
} else {
|
||||
ag.Shared = APIAlertGroupSharedMaps{
|
||||
Labels: map[string]string{},
|
||||
Annotations: Annotations{},
|
||||
Silences: map[string]string{},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,10 +4,16 @@ import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/pmezard/go-difflib/difflib"
|
||||
|
||||
"github.com/prymitive/karma/internal/models"
|
||||
)
|
||||
|
||||
func TestDedupSharedMaps(t *testing.T) {
|
||||
am := models.AlertmanagerInstance{
|
||||
Cluster: "fakeCluster",
|
||||
SilencedBy: []string{"fakeSilenceID"},
|
||||
}
|
||||
ag := models.APIAlertGroup{
|
||||
AlertGroup: models.AlertGroup{
|
||||
Labels: map[string]string{
|
||||
@@ -15,6 +21,7 @@ func TestDedupSharedMaps(t *testing.T) {
|
||||
},
|
||||
Alerts: models.AlertList{
|
||||
models.Alert{
|
||||
State: models.AlertStateSuppressed,
|
||||
Annotations: models.Annotations{
|
||||
models.Annotation{
|
||||
Name: "summary",
|
||||
@@ -30,8 +37,10 @@ func TestDedupSharedMaps(t *testing.T) {
|
||||
"job": "node_exporter",
|
||||
"instance": "1",
|
||||
},
|
||||
Alertmanager: []models.AlertmanagerInstance{am},
|
||||
},
|
||||
models.Alert{
|
||||
State: models.AlertStateSuppressed,
|
||||
Annotations: models.Annotations{
|
||||
models.Annotation{
|
||||
Name: "summary",
|
||||
@@ -43,8 +52,10 @@ func TestDedupSharedMaps(t *testing.T) {
|
||||
"job": "node_exporter",
|
||||
"instance": "2",
|
||||
},
|
||||
Alertmanager: []models.AlertmanagerInstance{am},
|
||||
},
|
||||
models.Alert{
|
||||
State: models.AlertStateSuppressed,
|
||||
Annotations: models.Annotations{
|
||||
models.Annotation{
|
||||
Name: "summary",
|
||||
@@ -56,6 +67,7 @@ func TestDedupSharedMaps(t *testing.T) {
|
||||
"job": "blackbox",
|
||||
"instance": "3",
|
||||
},
|
||||
Alertmanager: []models.AlertmanagerInstance{am},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -83,8 +95,21 @@ func TestDedupSharedMaps(t *testing.T) {
|
||||
},
|
||||
"startsAt": "0001-01-01T00:00:00Z",
|
||||
"endsAt": "0001-01-01T00:00:00Z",
|
||||
"state": "",
|
||||
"alertmanager": null,
|
||||
"state": "suppressed",
|
||||
"alertmanager": [
|
||||
{
|
||||
"name": "",
|
||||
"cluster": "fakeCluster",
|
||||
"state": "",
|
||||
"startsAt": "0001-01-01T00:00:00Z",
|
||||
"endsAt": "0001-01-01T00:00:00Z",
|
||||
"source": "",
|
||||
"silencedBy": [
|
||||
"fakeSilenceID"
|
||||
],
|
||||
"inhibitedBy": null
|
||||
}
|
||||
],
|
||||
"receiver": ""
|
||||
},
|
||||
{
|
||||
@@ -95,8 +120,21 @@ func TestDedupSharedMaps(t *testing.T) {
|
||||
},
|
||||
"startsAt": "0001-01-01T00:00:00Z",
|
||||
"endsAt": "0001-01-01T00:00:00Z",
|
||||
"state": "",
|
||||
"alertmanager": null,
|
||||
"state": "suppressed",
|
||||
"alertmanager": [
|
||||
{
|
||||
"name": "",
|
||||
"cluster": "fakeCluster",
|
||||
"state": "",
|
||||
"startsAt": "0001-01-01T00:00:00Z",
|
||||
"endsAt": "0001-01-01T00:00:00Z",
|
||||
"source": "",
|
||||
"silencedBy": [
|
||||
"fakeSilenceID"
|
||||
],
|
||||
"inhibitedBy": null
|
||||
}
|
||||
],
|
||||
"receiver": ""
|
||||
},
|
||||
{
|
||||
@@ -107,8 +145,21 @@ func TestDedupSharedMaps(t *testing.T) {
|
||||
},
|
||||
"startsAt": "0001-01-01T00:00:00Z",
|
||||
"endsAt": "0001-01-01T00:00:00Z",
|
||||
"state": "",
|
||||
"alertmanager": null,
|
||||
"state": "suppressed",
|
||||
"alertmanager": [
|
||||
{
|
||||
"name": "",
|
||||
"cluster": "fakeCluster",
|
||||
"state": "",
|
||||
"startsAt": "0001-01-01T00:00:00Z",
|
||||
"endsAt": "0001-01-01T00:00:00Z",
|
||||
"source": "",
|
||||
"silencedBy": [
|
||||
"fakeSilenceID"
|
||||
],
|
||||
"inhibitedBy": null
|
||||
}
|
||||
],
|
||||
"receiver": ""
|
||||
}
|
||||
],
|
||||
@@ -125,13 +176,27 @@ func TestDedupSharedMaps(t *testing.T) {
|
||||
"isLink": false
|
||||
}
|
||||
],
|
||||
"labels": {}
|
||||
"labels": {},
|
||||
"silences": {
|
||||
"fakeCluster": "fakeSilenceID"
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
agJSON, _ := json.MarshalIndent(ag, "", " ")
|
||||
if string(agJSON) != expectedJSON {
|
||||
t.Errorf("Expected: %s\nGot: %s\n", expectedJSON, string(agJSON))
|
||||
diff := difflib.UnifiedDiff{
|
||||
A: difflib.SplitLines(expectedJSON),
|
||||
B: difflib.SplitLines(string(agJSON)),
|
||||
FromFile: "Expected",
|
||||
ToFile: "Current",
|
||||
Context: 3,
|
||||
}
|
||||
text, err := difflib.GetUnifiedDiffString(diff)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
t.Errorf("JSON mismatch:\n%s", text)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user