@@ -39,12 +39,18 @@ func extractObjectQueriesResult[T Comment | Downtime | HostServiceRuntimeAttribu
39
39
}
40
40
41
41
// queryObjectsApi performs a configurable HTTP request against the Icinga 2 API and returns its raw response.
42
- func (client * Client ) queryObjectsApi (urlPaths []string , method string , body io.Reader , headers map [string ]string ) (io.ReadCloser , error ) {
42
+ func (client * Client ) queryObjectsApi (
43
+ ctx context.Context ,
44
+ urlPaths []string ,
45
+ method string ,
46
+ body io.Reader ,
47
+ headers map [string ]string ,
48
+ ) (io.ReadCloser , error ) {
43
49
apiUrl , err := url .JoinPath (client .ApiHost , urlPaths ... )
44
50
if err != nil {
45
51
return nil , err
46
52
}
47
- req , err := http .NewRequestWithContext (client . Ctx , method , apiUrl , body )
53
+ req , err := http .NewRequestWithContext (ctx , method , apiUrl , body )
48
54
if err != nil {
49
55
return nil , err
50
56
}
@@ -72,22 +78,24 @@ func (client *Client) queryObjectsApi(urlPaths []string, method string, body io.
72
78
}
73
79
74
80
// queryObjectsApiDirect performs a direct resp. "fast" API query against an object, optionally identified by its name.
75
- func (client * Client ) queryObjectsApiDirect (objType , objName string ) (io.ReadCloser , error ) {
81
+ func (client * Client ) queryObjectsApiDirect (ctx context. Context , objType , objName string ) (io.ReadCloser , error ) {
76
82
return client .queryObjectsApi (
83
+ ctx ,
77
84
[]string {"/v1/objects/" , objType + "s/" , rawurlencode (objName )},
78
85
http .MethodGet ,
79
86
nil ,
80
87
map [string ]string {"Accept" : "application/json" })
81
88
}
82
89
83
90
// queryObjectsApiQuery sends a query to the Icinga 2 API /v1/objects to receive data of the given objType.
84
- func (client * Client ) queryObjectsApiQuery (objType string , query map [string ]any ) (io.ReadCloser , error ) {
91
+ func (client * Client ) queryObjectsApiQuery (ctx context. Context , objType string , query map [string ]any ) (io.ReadCloser , error ) {
85
92
reqBody , err := json .Marshal (query )
86
93
if err != nil {
87
94
return nil , err
88
95
}
89
96
90
97
return client .queryObjectsApi (
98
+ ctx ,
91
99
[]string {"/v1/objects/" , objType + "s" },
92
100
http .MethodPost ,
93
101
bytes .NewReader (reqBody ),
@@ -98,27 +106,15 @@ func (client *Client) queryObjectsApiQuery(objType string, query map[string]any)
98
106
})
99
107
}
100
108
101
- // fetchHostGroups fetches all Host Groups for this host.
102
- func (client * Client ) fetchHostGroups (host string ) ([]string , error ) {
103
- jsonRaw , err := client .queryObjectsApiDirect ("host" , host )
104
- if err != nil {
105
- return nil , err
106
- }
107
- objQueriesResults , err := extractObjectQueriesResult [HostServiceRuntimeAttributes ](jsonRaw )
108
- if err != nil {
109
- return nil , err
110
- }
111
-
112
- if len (objQueriesResults ) != 1 {
113
- return nil , fmt .Errorf ("expected exactly one result for host %q instead of %d" , host , len (objQueriesResults ))
109
+ // fetchHostServiceGroups fetches all Host or, if service is not empty, Service groups.
110
+ func (client * Client ) fetchHostServiceGroups (ctx context.Context , host , service string ) ([]string , error ) {
111
+ objType , objName := "host" , host
112
+ if service != "" {
113
+ objType = "service"
114
+ objName += "!" + service
114
115
}
115
116
116
- return objQueriesResults [0 ].Attrs .Groups , nil
117
- }
118
-
119
- // fetchServiceGroups fetches all Service Groups for this service on this host.
120
- func (client * Client ) fetchServiceGroups (host , service string ) ([]string , error ) {
121
- jsonRaw , err := client .queryObjectsApiDirect ("service" , host + "!" + service )
117
+ jsonRaw , err := client .queryObjectsApiDirect (ctx , objType , objName )
122
118
if err != nil {
123
119
return nil , err
124
120
}
@@ -128,7 +124,8 @@ func (client *Client) fetchServiceGroups(host, service string) ([]string, error)
128
124
}
129
125
130
126
if len (objQueriesResults ) != 1 {
131
- return nil , fmt .Errorf ("expected exactly one result for service %q instead of %d" , host + "!" + service , len (objQueriesResults ))
127
+ return nil , fmt .Errorf ("expected exactly one result for object type %q and %q instead of %d" ,
128
+ objType , objName , len (objQueriesResults ))
132
129
}
133
130
134
131
return objQueriesResults [0 ].Attrs .Groups , nil
@@ -140,15 +137,15 @@ func (client *Client) fetchServiceGroups(host, service string) ([]string, error)
140
137
// closest we can do, is query for Comments with the Acknowledgement Service Type and the host/service name. In addition,
141
138
// the Host's resp. Service's AcknowledgementLastChange field has NOT the same timestamp as the Comment; there is a
142
139
// difference of some milliseconds. As there might be even multiple ACK comments, we have to find the closest one.
143
- func (client * Client ) fetchAcknowledgementComment (host , service string , ackTime time.Time ) (* Comment , error ) {
140
+ func (client * Client ) fetchAcknowledgementComment (ctx context. Context , host , service string , ackTime time.Time ) (* Comment , error ) {
144
141
filterExpr := "comment.entry_type == 4 && comment.host_name == comment_host_name"
145
142
filterVars := map [string ]string {"comment_host_name" : host }
146
143
if service != "" {
147
144
filterExpr += " && comment.service_name == comment_service_name"
148
145
filterVars ["comment_service_name" ] = service
149
146
}
150
147
151
- jsonRaw , err := client .queryObjectsApiQuery ("comment" , map [string ]any {"filter" : filterExpr , "filter_vars" : filterVars })
148
+ jsonRaw , err := client .queryObjectsApiQuery (ctx , "comment" , map [string ]any {"filter" : filterExpr , "filter_vars" : filterVars })
152
149
if err != nil {
153
150
return nil , err
154
151
}
@@ -175,39 +172,20 @@ func (client *Client) fetchAcknowledgementComment(host, service string, ackTime
175
172
176
173
// checkMissedChanges queries for Service or Host objects to handle missed elements.
177
174
//
178
- // If a filterExpr is given (non-empty string), it will be used for the query. Otherwise, all objects will be requested.
179
- //
180
- // The callback function will be called f.e. object of the objType (i.e. "host" or "service") being retrieved from the
181
- // Icinga 2 Objects API sequentially. The callback function or a later caller decides if this object should be replayed.
182
- func (client * Client ) checkMissedChanges (
183
- objType , filterExpr string ,
184
- attrsCallbackFn func (attrs HostServiceRuntimeAttributes , host , service string ) error ,
185
- ) (err error ) {
186
- logger := client .Logger .With (zap .String ("object type" , objType ), zap .String ("filter expr" , filterExpr ))
187
-
188
- defer func () {
189
- if err != nil {
190
- logger .Errorw ("Querying API for replay failed" , zap .Error (err ))
191
- }
192
- }()
193
-
194
- var jsonRaw io.ReadCloser
195
- if filterExpr == "" {
196
- jsonRaw , err = client .queryObjectsApiDirect (objType , "" )
197
- } else {
198
- jsonRaw , err = client .queryObjectsApiQuery (objType , map [string ]any {"filter" : filterExpr })
199
- }
175
+ // If the object's acknowledgement field is non-zero, an Acknowledgement Event will be constructed following the Host or
176
+ // Service object.
177
+ func (client * Client ) checkMissedChanges (ctx context.Context , objType string ) error {
178
+ jsonRaw , err := client .queryObjectsApiDirect (ctx , objType , "" )
200
179
if err != nil {
201
- return
180
+ return err
202
181
}
203
182
204
183
objQueriesResults , err := extractObjectQueriesResult [HostServiceRuntimeAttributes ](jsonRaw )
205
184
if err != nil {
206
- return
185
+ return err
207
186
}
208
187
209
- logger .Debugw ("Querying API resulted in state changes" , zap .Int ("changes" , len (objQueriesResults )))
210
-
188
+ var stateChangeEvents , acknowledgementEvents int
211
189
for _ , objQueriesResult := range objQueriesResults {
212
190
var hostName , serviceName string
213
191
switch objQueriesResult .Type {
@@ -219,58 +197,58 @@ func (client *Client) checkMissedChanges(
219
197
serviceName = objQueriesResult .Attrs .Name
220
198
221
199
default :
222
- err = fmt .Errorf ("querying API delivered a wrong object type %q" , objQueriesResult .Type )
223
- return
200
+ return fmt .Errorf ("querying API delivered a wrong object type %q" , objQueriesResult .Type )
224
201
}
225
202
226
- err = attrsCallbackFn (objQueriesResult .Attrs , hostName , serviceName )
203
+ // State change event first
204
+ ev , err := client .buildHostServiceEvent (
205
+ ctx ,
206
+ objQueriesResult .Attrs .LastCheckResult , objQueriesResult .Attrs .State ,
207
+ hostName , serviceName )
227
208
if err != nil {
228
- return
209
+ return fmt . Errorf ( "failed to construct Event from Host/Service response, %w" , err )
229
210
}
230
- }
231
- return
232
- }
233
-
234
- // checkMissedStateChanges fetches all objects of the requested type and feeds them into the handler.
235
- func (client * Client ) checkMissedStateChanges (ctx context.Context , objType string ) error {
236
- return client .checkMissedChanges (objType , "" , func (attrs HostServiceRuntimeAttributes , host , service string ) error {
237
- ev , err := client .buildHostServiceEvent (attrs .LastCheckResult , attrs .State , host , service )
238
- if err != nil {
239
- return fmt .Errorf ("failed to construct Event from API, %w" , err )
240
- }
241
-
242
211
select {
243
212
case <- ctx .Done ():
244
213
return ctx .Err ()
245
- case client .eventDispatcherReplay <- & eventMsg {ev , attrs .LastStateChange .Time }:
246
- return nil
214
+ case client .eventDispatcherReplay <- & eventMsg {ev , objQueriesResult . Attrs .LastStateChange .Time }:
215
+ stateChangeEvents ++
247
216
}
248
- })
249
- }
250
217
251
- // checkMissedAcknowledgements fetches all Host or Service Acknowledgements and feeds them into the handler.
252
- //
253
- // Currently only active acknowledgements are being processed.
254
- func (client * Client ) checkMissedAcknowledgements (ctx context.Context , objType string ) error {
255
- filterExpr := fmt .Sprintf ("%s.acknowledgement" , objType )
256
- return client .checkMissedChanges (objType , filterExpr , func (attrs HostServiceRuntimeAttributes , host , service string ) error {
257
- ackComment , err := client .fetchAcknowledgementComment (host , service , attrs .AcknowledgementLastChange .Time )
258
- if err != nil {
259
- return fmt .Errorf ("cannot fetch ACK Comment for Acknowledgement, %w" , err )
218
+ // Optional acknowledgement event second
219
+ if objQueriesResult .Attrs .Acknowledgement == 0 {
220
+ continue
260
221
}
261
222
262
- ev , err := client .buildAcknowledgementEvent (host , service , ackComment .Author , ackComment .Text )
223
+ ackComment , err := client .fetchAcknowledgementComment (
224
+ ctx ,
225
+ hostName , serviceName ,
226
+ objQueriesResult .Attrs .AcknowledgementLastChange .Time )
263
227
if err != nil {
264
- return fmt .Errorf ("failed to construct Event from Acknowledgement API , %w" , err )
228
+ return fmt .Errorf ("fetching acknowledgement comment for %v failed , %w" , ev , err )
265
229
}
266
230
231
+ ev , err = client .buildAcknowledgementEvent (
232
+ ctx ,
233
+ hostName , serviceName ,
234
+ ackComment .Author , ackComment .Text )
235
+ if err != nil {
236
+ return fmt .Errorf ("failed to construct Event from Acknowledgement response, %w" , err )
237
+ }
267
238
select {
268
239
case <- ctx .Done ():
269
240
return ctx .Err ()
270
- case client .eventDispatcherReplay <- & eventMsg {ev , attrs . AcknowledgementLastChange .Time }:
271
- return nil
241
+ case client .eventDispatcherReplay <- & eventMsg {ev , objQueriesResult . Attrs . LastStateChange .Time }:
242
+ acknowledgementEvents ++
272
243
}
273
- })
244
+ }
245
+
246
+ client .Logger .Infow ("Replaying API emitted state changes" ,
247
+ zap .String ("object type" , objType ),
248
+ zap .Int ("state changes" , stateChangeEvents ),
249
+ zap .Int ("acknowledgements" , acknowledgementEvents ))
250
+
251
+ return nil
274
252
}
275
253
276
254
// connectEventStream connects to the EventStream within an infinite loop until a connection was established.
@@ -390,11 +368,11 @@ func (client *Client) listenEventStream() error {
390
368
)
391
369
switch respT := resp .(type ) {
392
370
case * StateChange :
393
- ev , err = client .buildHostServiceEvent (respT .CheckResult , respT .State , respT .Host , respT .Service )
371
+ ev , err = client .buildHostServiceEvent (client . Ctx , respT .CheckResult , respT .State , respT .Host , respT .Service )
394
372
evTime = respT .Timestamp .Time
395
373
396
374
case * AcknowledgementSet :
397
- ev , err = client .buildAcknowledgementEvent (respT .Host , respT .Service , respT .Author , respT .Comment )
375
+ ev , err = client .buildAcknowledgementEvent (client . Ctx , respT .Host , respT .Service , respT .Author , respT .Comment )
398
376
evTime = respT .Timestamp .Time
399
377
400
378
// case *AcknowledgementCleared:
0 commit comments