Skip to content

Commit 2572f2b

Browse files
mlabouardyotherpirate
authored andcommitted
Add Icinga2 input plugin (influxdata#4559)
1 parent 06f8661 commit 2572f2b

File tree

5 files changed

+328
-0
lines changed

5 files changed

+328
-0
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ configuration options.
168168
* [http_listener](./plugins/inputs/http_listener)
169169
* [http](./plugins/inputs/http) (generic HTTP plugin, supports using input data formats)
170170
* [http_response](./plugins/inputs/http_response)
171+
* [icinga2](./plugins/inputs/icinga2)
171172
* [influxdb](./plugins/inputs/influxdb)
172173
* [internal](./plugins/inputs/internal)
173174
* [interrupts](./plugins/inputs/interrupts)

plugins/inputs/all/all.go

+1
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import (
4242
_ "github.com/influxdata/telegraf/plugins/inputs/http_listener"
4343
_ "github.com/influxdata/telegraf/plugins/inputs/http_response"
4444
_ "github.com/influxdata/telegraf/plugins/inputs/httpjson"
45+
_ "github.com/influxdata/telegraf/plugins/inputs/icinga2"
4546
_ "github.com/influxdata/telegraf/plugins/inputs/influxdb"
4647
_ "github.com/influxdata/telegraf/plugins/inputs/internal"
4748
_ "github.com/influxdata/telegraf/plugins/inputs/interrupts"

plugins/inputs/icinga2/README.md

+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# Icinga2 Input Plugin
2+
3+
This plugin gather services & hosts status using Icinga2 Remote API.
4+
5+
The icinga2 plugin uses the icinga2 remote API to gather status on running
6+
services and hosts. You can read Icinga2's documentation for their remote API
7+
[here](https://docs.icinga.com/icinga2/latest/doc/module/icinga2/chapter/icinga2-api)
8+
9+
### Configuration:
10+
11+
```toml
12+
# Description
13+
[[inputs.icinga2]]
14+
## Required Icinga2 server address (default: "https://localhost:5665")
15+
# server = "https://localhost:5665"
16+
17+
## Required Icinga2 object type ("services" or "hosts, default "services")
18+
# object_type = "services"
19+
20+
## Credentials for basic HTTP authentication
21+
# username = "admin"
22+
# password = "admin"
23+
24+
## Maximum time to receive response.
25+
# response_timeout = "5s"
26+
27+
## Optional TLS Config
28+
# tls_ca = "/etc/telegraf/ca.pem"
29+
# tls_cert = "/etc/telegraf/cert.pem"
30+
# tls_key = "/etc/telegraf/key.pem"
31+
## Use TLS but skip chain & host verification
32+
# insecure_skip_verify = true
33+
```
34+
35+
### Measurements & Fields:
36+
37+
- All measurements have the following fields:
38+
- name (string)
39+
- state_code (int)
40+
41+
### Tags:
42+
43+
- All measurements have the following tags:
44+
- check_command
45+
- display_name
46+
- state
47+
- source
48+
- port
49+
- scheme
50+
51+
### Sample Queries:
52+
53+
```
54+
SELECT * FROM "icinga2_services" WHERE state_code = 0 AND time > now() - 24h // Service with OK status
55+
SELECT * FROM "icinga2_services" WHERE state_code = 1 AND time > now() - 24h // Service with WARNING status
56+
SELECT * FROM "icinga2_services" WHERE state_code = 2 AND time > now() - 24h // Service with CRITICAL status
57+
SELECT * FROM "icinga2_services" WHERE state_code = 3 AND time > now() - 24h // Service with UNKNOWN status
58+
```
59+
60+
### Example Output:
61+
62+
```
63+
$ ./telegraf -config telegraf.conf -input-filter icinga2 -test
64+
icinga2_hosts,display_name=router-fr.eqx.fr,check_command=hostalive-custom,host=test-vm,source=localhost,port=5665,scheme=https,state=ok name="router-fr.eqx.fr",state=0 1492021603000000000
65+
```

plugins/inputs/icinga2/icinga2.go

+170
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
package icinga2
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"log"
7+
"net/http"
8+
"net/url"
9+
"time"
10+
11+
"github.com/influxdata/telegraf"
12+
"github.com/influxdata/telegraf/internal"
13+
"github.com/influxdata/telegraf/internal/tls"
14+
"github.com/influxdata/telegraf/plugins/inputs"
15+
)
16+
17+
type Icinga2 struct {
18+
Server string
19+
ObjectType string
20+
Username string
21+
Password string
22+
ResponseTimeout internal.Duration
23+
tls.ClientConfig
24+
25+
client *http.Client
26+
}
27+
28+
type Result struct {
29+
Results []Object `json:"results"`
30+
}
31+
32+
type Object struct {
33+
Attrs Attribute `json:"attrs"`
34+
Name string `json:"name"`
35+
Joins struct{} `json:"joins"`
36+
Meta struct{} `json:"meta"`
37+
Type ObjectType `json:"type"`
38+
}
39+
40+
type Attribute struct {
41+
CheckCommand string `json:"check_command"`
42+
DisplayName string `json:"display_name"`
43+
Name string `json:"name"`
44+
State int `json:"state"`
45+
}
46+
47+
var levels = []string{"ok", "warning", "critical", "unknown"}
48+
49+
type ObjectType string
50+
51+
var sampleConfig = `
52+
## Required Icinga2 server address (default: "https://localhost:5665")
53+
# server = "https://localhost:5665"
54+
55+
## Required Icinga2 object type ("services" or "hosts, default "services")
56+
# object_type = "services"
57+
58+
## Credentials for basic HTTP authentication
59+
# username = "admin"
60+
# password = "admin"
61+
62+
## Maximum time to receive response.
63+
# response_timeout = "5s"
64+
65+
## Optional TLS Config
66+
# tls_ca = "/etc/telegraf/ca.pem"
67+
# tls_cert = "/etc/telegraf/cert.pem"
68+
# tls_key = "/etc/telegraf/key.pem"
69+
## Use TLS but skip chain & host verification
70+
# insecure_skip_verify = true
71+
`
72+
73+
func (i *Icinga2) Description() string {
74+
return "Gather Icinga2 status"
75+
}
76+
77+
func (i *Icinga2) SampleConfig() string {
78+
return sampleConfig
79+
}
80+
81+
func (i *Icinga2) GatherStatus(acc telegraf.Accumulator, checks []Object) {
82+
for _, check := range checks {
83+
fields := make(map[string]interface{})
84+
tags := make(map[string]string)
85+
86+
url, err := url.Parse(i.Server)
87+
if err != nil {
88+
log.Fatal(err)
89+
}
90+
91+
fields["name"] = check.Attrs.Name
92+
fields["state_code"] = check.Attrs.State
93+
94+
tags["display_name"] = check.Attrs.DisplayName
95+
tags["check_command"] = check.Attrs.CheckCommand
96+
tags["state"] = levels[check.Attrs.State]
97+
tags["source"] = url.Hostname()
98+
tags["scheme"] = url.Scheme
99+
tags["port"] = url.Port()
100+
101+
acc.AddFields(fmt.Sprintf("icinga2_%s", i.ObjectType), fields, tags)
102+
}
103+
}
104+
105+
func (i *Icinga2) createHttpClient() (*http.Client, error) {
106+
tlsCfg, err := i.ClientConfig.TLSConfig()
107+
if err != nil {
108+
return nil, err
109+
}
110+
111+
client := &http.Client{
112+
Transport: &http.Transport{
113+
TLSClientConfig: tlsCfg,
114+
},
115+
Timeout: i.ResponseTimeout.Duration,
116+
}
117+
118+
return client, nil
119+
}
120+
121+
func (i *Icinga2) Gather(acc telegraf.Accumulator) error {
122+
if i.ResponseTimeout.Duration < time.Second {
123+
i.ResponseTimeout.Duration = time.Second * 5
124+
}
125+
126+
if i.client == nil {
127+
client, err := i.createHttpClient()
128+
if err != nil {
129+
return err
130+
}
131+
i.client = client
132+
}
133+
134+
url := fmt.Sprintf("%s/v1/objects/%s?attrs=name&attrs=display_name&attrs=state&attrs=check_command", i.Server, i.ObjectType)
135+
136+
req, err := http.NewRequest("GET", url, nil)
137+
if err != nil {
138+
return err
139+
}
140+
141+
if i.Username != "" {
142+
req.SetBasicAuth(i.Username, i.Password)
143+
}
144+
145+
resp, err := i.client.Do(req)
146+
if err != nil {
147+
return err
148+
}
149+
150+
defer resp.Body.Close()
151+
152+
result := Result{}
153+
json.NewDecoder(resp.Body).Decode(&result)
154+
if err != nil {
155+
return err
156+
}
157+
158+
i.GatherStatus(acc, result.Results)
159+
160+
return nil
161+
}
162+
163+
func init() {
164+
inputs.Add("icinga2", func() telegraf.Input {
165+
return &Icinga2{
166+
Server: "https://localhost:5665",
167+
ObjectType: "services",
168+
}
169+
})
170+
}
+91
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
package icinga2
2+
3+
import (
4+
"encoding/json"
5+
"testing"
6+
7+
"github.com/influxdata/telegraf/testutil"
8+
)
9+
10+
func TestGatherServicesStatus(t *testing.T) {
11+
12+
s := `{"results":[
13+
{
14+
"attrs": {
15+
"check_command": "check-bgp-juniper-netconf",
16+
"display_name": "eq-par.dc2.fr",
17+
"name": "ef017af8-c684-4f3f-bb20-0dfe9fcd3dbe",
18+
"state": 0
19+
},
20+
"joins": {},
21+
"meta": {},
22+
"name": "eq-par.dc2.fr!ef017af8-c684-4f3f-bb20-0dfe9fcd3dbe",
23+
"type": "Service"
24+
}
25+
]}`
26+
27+
checks := Result{}
28+
json.Unmarshal([]byte(s), &checks)
29+
fields := map[string]interface{}{
30+
"name": "ef017af8-c684-4f3f-bb20-0dfe9fcd3dbe",
31+
"state_code": 0,
32+
}
33+
tags := map[string]string{
34+
"display_name": "eq-par.dc2.fr",
35+
"check_command": "check-bgp-juniper-netconf",
36+
"state": "ok",
37+
"source": "localhost",
38+
"port": "5665",
39+
"scheme": "https",
40+
}
41+
42+
var acc testutil.Accumulator
43+
44+
icinga2 := new(Icinga2)
45+
icinga2.ObjectType = "services"
46+
icinga2.Server = "https://localhost:5665"
47+
icinga2.GatherStatus(&acc, checks.Results)
48+
acc.AssertContainsTaggedFields(t, "icinga2_services", fields, tags)
49+
}
50+
51+
func TestGatherHostsStatus(t *testing.T) {
52+
53+
s := `{"results":[
54+
{
55+
"attrs": {
56+
"name": "webserver",
57+
"address": "192.168.1.1",
58+
"check_command": "ping",
59+
"display_name": "apache",
60+
"state": 2
61+
},
62+
"joins": {},
63+
"meta": {},
64+
"name": "webserver",
65+
"type": "Host"
66+
}
67+
]}`
68+
69+
checks := Result{}
70+
json.Unmarshal([]byte(s), &checks)
71+
fields := map[string]interface{}{
72+
"name": "webserver",
73+
"state_code": 2,
74+
}
75+
tags := map[string]string{
76+
"display_name": "apache",
77+
"check_command": "ping",
78+
"state": "critical",
79+
"source": "localhost",
80+
"port": "5665",
81+
"scheme": "https",
82+
}
83+
84+
var acc testutil.Accumulator
85+
86+
icinga2 := new(Icinga2)
87+
icinga2.ObjectType = "hosts"
88+
icinga2.Server = "https://localhost:5665"
89+
icinga2.GatherStatus(&acc, checks.Results)
90+
acc.AssertContainsTaggedFields(t, "icinga2_hosts", fields, tags)
91+
}

0 commit comments

Comments
 (0)