Skip to content

Commit 8ff387b

Browse files
committed
Ignore flapping state if flapping detection isn't enabled
1 parent e954e40 commit 8ff387b

File tree

4 files changed

+70
-9
lines changed

4 files changed

+70
-9
lines changed

internal/icinga2/api_responses.go

+9
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ type HostServiceRuntimeAttributes struct {
137137
Acknowledgement int `json:"acknowledgement"`
138138
IsFlapping bool `json:"flapping"`
139139
AcknowledgementLastChange UnixFloat `json:"acknowledgement_last_change"`
140+
EnableFlapping bool `json:"enable_flapping"`
140141
}
141142

142143
// ObjectQueriesResult represents the Icinga 2 API Object Queries Result wrapper object.
@@ -288,6 +289,14 @@ type ObjectCreatedDeleted struct {
288289
EventType string `json:"type"`
289290
}
290291

292+
// IcingaAppStatus represents the Icinga 2 API status endpoint query result of type IcingaApplication.
293+
// https://icinga.com/docs/icinga-2/latest/doc/12-icinga2-api/#status-and-statistics
294+
type IcingaAppStatus struct {
295+
App struct {
296+
EnableFlapping bool `json:"enable_flapping"`
297+
} `json:"app"`
298+
}
299+
291300
// UnmarshalEventStreamResponse unmarshal a JSON response line from the Icinga 2 API Event Stream.
292301
//
293302
// The function expects an Icinga 2 API Event Stream Response in its JSON form and tries to unmarshal it into one of the

internal/icinga2/client.go

+9-3
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,9 @@ func (client *Client) buildAcknowledgementEvent(ctx context.Context, ack *Acknow
265265
if err != nil {
266266
return nil, err
267267
}
268-
if !isMuted(queryResult) {
268+
if muted, err := isMuted(ctx, client, queryResult); err != nil {
269+
return nil, err
270+
} else if !muted {
269271
ev.Message = queryResult.Attrs.LastCheckResult.Output
270272
ev.SetMute(false, "Acknowledgement cleared")
271273
}
@@ -310,7 +312,9 @@ func (client *Client) buildDowntimeEvent(ctx context.Context, d Downtime, startE
310312
if err != nil {
311313
return nil, err
312314
}
313-
if !isMuted(queryResult) {
315+
if muted, err := isMuted(ctx, client, queryResult); err != nil {
316+
return nil, err
317+
} else if !muted {
314318
// When a downtime is cancelled/expired and there's no other active downtime/ack, we're going to send some
315319
// notifications if there's still an active incident. Therefore, we need the most recent CheckResult of
316320
// that Checkable to use it for the notifications.
@@ -347,7 +351,9 @@ func (client *Client) buildFlappingEvent(ctx context.Context, flapping *Flapping
347351
if err != nil {
348352
return nil, err
349353
}
350-
if !isMuted(queryResult) {
354+
if muted, err := isMuted(ctx, client, queryResult); err != nil {
355+
return nil, err
356+
} else if !muted {
351357
ev.Message = queryResult.Attrs.LastCheckResult.Output
352358
ev.SetMute(false, reason)
353359
}

internal/icinga2/client_api.go

+34-4
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,31 @@ func (client *Client) queryObjectsApiDirect(ctx context.Context, objType, objNam
130130
map[string]string{"Accept": "application/json"})
131131
}
132132

133+
// fetchIcingaAppStatus retrieves the global state of the IcingaApplication type via the /v1/status endpoint.
134+
func (client *Client) fetchIcingaAppStatus(ctx context.Context) (*IcingaAppStatus, error) {
135+
response, err := client.queryObjectsApi(
136+
ctx,
137+
[]string{"/v1/status/IcingaApplication/"},
138+
http.MethodGet,
139+
nil,
140+
map[string]string{"Accept": "application/json"})
141+
if err != nil {
142+
return nil, err
143+
}
144+
145+
defer func() {
146+
_, _ = io.Copy(io.Discard, response)
147+
_ = response.Close()
148+
}()
149+
150+
status := new(IcingaAppStatus)
151+
if err := json.NewDecoder(response).Decode(status); err != nil {
152+
return nil, err
153+
}
154+
155+
return status, nil
156+
}
157+
133158
// queryObjectsApiQuery sends a query to the Icinga 2 API /v1/objects to receive data of the given objType.
134159
func (client *Client) queryObjectsApiQuery(ctx context.Context, objType string, query map[string]any) (io.ReadCloser, error) {
135160
reqBody, err := json.Marshal(query)
@@ -260,6 +285,11 @@ func (client *Client) checkMissedChanges(ctx context.Context, objType string, ca
260285
}
261286

262287
attrs := objQueriesResult.Attrs
288+
checkableIsMuted, err := isMuted(ctx, client, &objQueriesResult)
289+
if err != nil {
290+
return err
291+
}
292+
263293
var fakeEv *event.Event
264294
if attrs.Acknowledgement != AcknowledgementNone {
265295
ackComment, err := client.fetchAcknowledgementComment(ctx, hostName, serviceName, attrs.AcknowledgementLastChange.Time())
@@ -275,17 +305,17 @@ func (client *Client) checkMissedChanges(ctx context.Context, objType string, ca
275305
if err != nil {
276306
return fmt.Errorf("failed to construct Event from Acknowledgement response, %w", err)
277307
}
278-
} else if isMuted(&objQueriesResult) {
308+
} else if checkableIsMuted {
279309
fakeEv, err = client.buildCommonEvent(ctx, hostName, serviceName)
280310
if err != nil {
281311
return fmt.Errorf("failed to construct checkable fake mute event: %w", err)
282312
}
283313

284314
fakeEv.Type = event.TypeMute
285-
if attrs.IsFlapping {
286-
fakeEv.SetMute(true, "Checkable is flapping, but we missed the Icinga 2 FlappingStart event")
287-
} else {
315+
if attrs.DowntimeDepth != 0 {
288316
fakeEv.SetMute(true, "Checkable is in downtime, but we missed the Icinga 2 DowntimeStart event")
317+
} else {
318+
fakeEv.SetMute(true, "Checkable is flapping, but we missed the Icinga 2 FlappingStart event")
289319
}
290320
} else {
291321
// This could potentially produce numerous superfluous database (event table) entries if we generate such

internal/icinga2/util.go

+18-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package icinga2
22

33
import (
4+
"context"
45
"net/url"
56
"strings"
67
)
@@ -18,6 +19,21 @@ func rawurlencode(s string) string {
1819
}
1920

2021
// isMuted returns true if the given checkable is either in Downtime, Flapping or acknowledged, otherwise false.
21-
func isMuted(checkable *ObjectQueriesResult[HostServiceRuntimeAttributes]) bool {
22-
return checkable.Attrs.IsFlapping || checkable.Attrs.Acknowledgement != AcknowledgementNone || checkable.Attrs.DowntimeDepth != 0
22+
// When the checkable is Flapping, and neither the flapping detection for that Checkable nor for the entire zone is
23+
// enabled, this will always return false.
24+
func isMuted(ctx context.Context, client *Client, checkable *ObjectQueriesResult[HostServiceRuntimeAttributes]) (bool, error) {
25+
if checkable.Attrs.Acknowledgement != AcknowledgementNone || checkable.Attrs.DowntimeDepth != 0 {
26+
return true, nil
27+
}
28+
29+
if checkable.Attrs.IsFlapping && checkable.Attrs.EnableFlapping {
30+
status, err := client.fetchIcingaAppStatus(ctx)
31+
if err != nil {
32+
return false, err
33+
}
34+
35+
return status.App.EnableFlapping, nil
36+
}
37+
38+
return false, nil
2339
}

0 commit comments

Comments
 (0)