Skip to content

Commit b32141b

Browse files
authored
Merge pull request #36 from thomasjpfan/issue-35
ENH: Adds alert persistent feature
2 parents c13d11a + 7f9690e commit b32141b

File tree

4 files changed

+81
-7
lines changed

4 files changed

+81
-7
lines changed

docs/usage.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ You can find more about scrapeType's on [Scrape Config](https://prometheus.io/do
3737
|alertLabels |This parameter is translated to Prometheus alert `LABELS` statement. It allows specifying a set of additional labels to be attached to the alert. Multiple labels can be separated with comma (`,`).<br>**Example:** `severity=high,receiver=system`|No|
3838
|alertName |The name of the alert. It is combined with the `serviceName` thus producing an unique identifier.<br>**Example:** `memoryAlert`|Yes|
3939
|serviceName |The name of the service. It is combined with the `alertName` thus producing an unique identifier.<br>**Example:** `go-demo`|Yes|
40+
|alertPersistent|When set to *true*, the alert will persist when the service is scaled to zero replicas.<br>**Example:** `true`|No|
4041

4142
Those parameters can be indexed so that multiple alerts can be defined for a service. Indexing is sequential and starts from 1. An example of indexed `alertName` could be `alertName.1=memload` and `alertName.2=diskload`.
4243

prometheus/types.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,7 @@ type Alert struct {
222222
AlertIf string `json:"alertIf,omitempty"`
223223
AlertLabels map[string]string `json:"alertLabels,omitempty"`
224224
AlertName string `json:"alertName"`
225+
AlertPersistent bool `json:"alertPersistent"`
225226
AlertNameFormatted string
226227
ServiceName string `json:"serviceName"`
227228
Replicas int `json:"replicas"`

server/server.go

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ func (s *serve) ReconfigureHandler(w http.ResponseWriter, req *http.Request) {
9797
logPrintf("Processing " + req.URL.String())
9898
req.ParseForm()
9999
scrape := s.getScrape(req)
100-
s.deleteAlerts(scrape.ServiceName)
100+
s.deleteAlerts(scrape.ServiceName, false)
101101
alerts := s.getAlerts(req)
102102
prometheus.WriteConfig(s.configPath, s.scrapes, s.alerts)
103103
err := prometheus.Reload()
@@ -115,7 +115,7 @@ func (s *serve) RemoveHandler(w http.ResponseWriter, req *http.Request) {
115115
serviceName := req.URL.Query().Get("serviceName")
116116
scrape := s.scrapes[serviceName]
117117
delete(s.scrapes, serviceName)
118-
alerts := s.deleteAlerts(serviceName)
118+
alerts := s.deleteAlerts(serviceName, true)
119119
prometheus.WriteConfig(s.configPath, s.scrapes, s.alerts)
120120
err := prometheus.Reload()
121121
statusCode := http.StatusOK
@@ -241,6 +241,8 @@ func (s *serve) getAlerts(req *http.Request) []prometheus.Alert {
241241
alertName := req.URL.Query().Get(fmt.Sprintf("alertName.%d", i))
242242
annotations := s.getMapFromString(req.URL.Query().Get(fmt.Sprintf("alertAnnotations.%d", i)))
243243
labels := s.getMapFromString(req.URL.Query().Get(fmt.Sprintf("alertLabels.%d", i)))
244+
persistant := req.URL.Query().Get(fmt.Sprintf("alertPersistent.%d", i)) == "true"
245+
244246
alert := prometheus.Alert{
245247
ServiceName: alertDecode.ServiceName,
246248
AlertName: alertName,
@@ -249,6 +251,7 @@ func (s *serve) getAlerts(req *http.Request) []prometheus.Alert {
249251
AlertAnnotations: annotations,
250252
AlertLabels: labels,
251253
Replicas: replicas,
254+
AlertPersistent: persistant,
252255
}
253256
s.formatAlert(&alert)
254257
if !s.isValidAlert(&alert) {
@@ -322,7 +325,6 @@ func GetShortcuts() map[string]AlertIfShortcut {
322325
logPrintf("YAML decoding reading %s, error: %v", path, err)
323326
continue
324327
}
325-
fmt.Println(secretShortcuts)
326328

327329
for k, v := range secretShortcuts {
328330
shortcuts[k] = v
@@ -483,13 +485,16 @@ func (s *serve) isValidAlert(alert *prometheus.Alert) bool {
483485
return len(alert.AlertName) > 0 && len(alert.AlertIf) > 0
484486
}
485487

486-
func (s *serve) deleteAlerts(serviceName string) []prometheus.Alert {
488+
func (s *serve) deleteAlerts(
489+
serviceName string, keepPersistantAlerts bool) []prometheus.Alert {
487490
alerts := []prometheus.Alert{}
488491
serviceNameFormatted := s.getNameFormatted(serviceName)
489492
for k, v := range s.alerts {
490493
if strings.HasPrefix(k, serviceNameFormatted) {
491-
alerts = append(alerts, v)
492-
delete(s.alerts, k)
494+
if !keepPersistantAlerts || !v.AlertPersistent {
495+
alerts = append(alerts, v)
496+
delete(s.alerts, k)
497+
}
493498
}
494499
}
495500
return alerts

server/server_test.go

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,57 @@ func (s *ServerTestSuite) Test_ReconfigureHandler_AddsAlert() {
231231
s.Equal(expected, serve.alerts[expected.AlertNameFormatted])
232232
}
233233

234+
func (s *ServerTestSuite) Test_ReconfigureHandler_UpdatesPersistantAlert() {
235+
// When service is reconfigured, all alert, including persistant alerts, will be removed
236+
// and queried again.
237+
expected := prometheus.Alert{
238+
ServiceName: "my-service",
239+
AlertName: "my-alert",
240+
AlertIf: "a>b",
241+
AlertFor: "my-for",
242+
AlertNameFormatted: "myservice_myalert",
243+
AlertAnnotations: map[string]string{"a1": "v1", "a2": "v2"},
244+
AlertLabels: map[string]string{"l1": "v1"},
245+
AlertPersistent: true,
246+
}
247+
rwMock := ResponseWriterMock{}
248+
addr := fmt.Sprintf(
249+
"/v1/docker-flow-monitor?serviceName=%s&alertName=%s&alertIf=%s&alertFor=%s&alertAnnotations=%s&alertLabels=%s&alertPersistent=%t",
250+
expected.ServiceName,
251+
expected.AlertName,
252+
url.QueryEscape(expected.AlertIf),
253+
expected.AlertFor,
254+
url.QueryEscape("a1=v1,a2=v2"),
255+
url.QueryEscape("l1=v1"),
256+
expected.AlertPersistent,
257+
)
258+
req, _ := http.NewRequest("GET", addr, nil)
259+
260+
serve := New()
261+
serve.ReconfigureHandler(rwMock, req)
262+
263+
s.Equal(expected, serve.alerts[expected.AlertNameFormatted])
264+
265+
// Change alert slightly
266+
expected.AlertIf = "a<b"
267+
rwMock = ResponseWriterMock{}
268+
addr = fmt.Sprintf(
269+
"/v1/docker-flow-monitor?serviceName=%s&alertName=%s&alertIf=%s&alertFor=%s&alertAnnotations=%s&alertLabels=%s&alertPersistent=%t",
270+
expected.ServiceName,
271+
expected.AlertName,
272+
url.QueryEscape(expected.AlertIf),
273+
expected.AlertFor,
274+
url.QueryEscape("a1=v1,a2=v2"),
275+
url.QueryEscape("l1=v1"),
276+
expected.AlertPersistent,
277+
)
278+
req, _ = http.NewRequest("GET", addr, nil)
279+
280+
serve.ReconfigureHandler(rwMock, req)
281+
282+
s.Equal(expected, serve.alerts[expected.AlertNameFormatted])
283+
}
284+
234285
func (s *ServerTestSuite) Test_ReconfigureHandler_ExpandsShortcuts() {
235286
testData := []struct {
236287
expected string
@@ -604,11 +655,12 @@ func (s *ServerTestSuite) Test_ReconfigureHandler_AddsMultipleAlerts() {
604655
AlertAnnotations: map[string]string{"annotation": fmt.Sprintf("annotation-value-%d", i)},
605656
AlertLabels: map[string]string{"label": fmt.Sprintf("label-value-%d", i)},
606657
Replicas: 3,
658+
AlertPersistent: i == 2,
607659
})
608660
}
609661
rwMock := ResponseWriterMock{}
610662
addr := fmt.Sprintf(
611-
"/v1/docker-flow-monitor?serviceName=%s&alertName.1=%s&alertIf.1=%s&alertFor.1=%s&alertName.2=%s&alertIf.2=%s&alertFor.2=%s&alertAnnotations.1=%s&alertAnnotations.2=%s&alertLabels.1=%s&alertLabels.2=%s&replicas=3",
663+
"/v1/docker-flow-monitor?serviceName=%s&alertName.1=%s&alertIf.1=%s&alertFor.1=%s&alertName.2=%s&alertIf.2=%s&alertFor.2=%s&alertAnnotations.1=%s&alertAnnotations.2=%s&alertLabels.1=%s&alertLabels.2=%s&replicas=3&alertPersistent.2=true",
612664
expected[0].ServiceName,
613665
expected[0].AlertName,
614666
expected[0].AlertIf,
@@ -1123,6 +1175,21 @@ func (s *ServerTestSuite) Test_RemoveHandler_RemovesAlerts() {
11231175
s.Len(serve.alerts, 1)
11241176
}
11251177

1178+
func (s *ServerTestSuite) Test_RemoveHandler_KeepsPersistantAlerts() {
1179+
1180+
rwMock := ResponseWriterMock{}
1181+
addr := "/v1/docker-flow-monitor?serviceName=my-service-1"
1182+
req, _ := http.NewRequest("DELETE", addr, nil)
1183+
1184+
serve := New()
1185+
serve.alerts["myservice1alert1"] = prometheus.Alert{ServiceName: "my-service-1", AlertName: "my-alert-1", AlertPersistent: true}
1186+
serve.alerts["myservice1alert2"] = prometheus.Alert{ServiceName: "my-service-1", AlertName: "my-alert-2"}
1187+
serve.alerts["myservice2alert1"] = prometheus.Alert{ServiceName: "my-service-2", AlertName: "my-alert-1"}
1188+
serve.RemoveHandler(rwMock, req)
1189+
1190+
s.Len(serve.alerts, 2)
1191+
}
1192+
11261193
func (s *ServerTestSuite) Test_RemoveHandler_ReturnsJson() {
11271194
reloadOrig := prometheus.Reload
11281195
defer func() { prometheus.Reload = reloadOrig }()

0 commit comments

Comments
 (0)