diff --git a/README.md b/README.md index e0d88e414..867aa10f7 100644 --- a/README.md +++ b/README.md @@ -168,6 +168,7 @@ configuration options. * [http_listener](./plugins/inputs/http_listener) * [http](./plugins/inputs/http) (generic HTTP plugin, supports using input data formats) * [http_response](./plugins/inputs/http_response) +* [icinga2](./plugins/inputs/icinga2) * [influxdb](./plugins/inputs/influxdb) * [internal](./plugins/inputs/internal) * [interrupts](./plugins/inputs/interrupts) diff --git a/plugins/inputs/all/all.go b/plugins/inputs/all/all.go index ac86fb879..17762d1c7 100644 --- a/plugins/inputs/all/all.go +++ b/plugins/inputs/all/all.go @@ -42,6 +42,7 @@ import ( _ "github.com/influxdata/telegraf/plugins/inputs/http_listener" _ "github.com/influxdata/telegraf/plugins/inputs/http_response" _ "github.com/influxdata/telegraf/plugins/inputs/httpjson" + _ "github.com/influxdata/telegraf/plugins/inputs/icinga2" _ "github.com/influxdata/telegraf/plugins/inputs/influxdb" _ "github.com/influxdata/telegraf/plugins/inputs/internal" _ "github.com/influxdata/telegraf/plugins/inputs/interrupts" diff --git a/plugins/inputs/icinga2/README.md b/plugins/inputs/icinga2/README.md new file mode 100644 index 000000000..697c6c59c --- /dev/null +++ b/plugins/inputs/icinga2/README.md @@ -0,0 +1,65 @@ +# Icinga2 Input Plugin + +This plugin gather services & hosts status using Icinga2 Remote API. + +The icinga2 plugin uses the icinga2 remote API to gather status on running +services and hosts. You can read Icinga2's documentation for their remote API +[here](https://docs.icinga.com/icinga2/latest/doc/module/icinga2/chapter/icinga2-api) + +### Configuration: + +```toml +# Description +[[inputs.icinga2]] + ## Required Icinga2 server address (default: "https://localhost:5665") + # server = "https://localhost:5665" + + ## Required Icinga2 object type ("services" or "hosts, default "services") + # object_type = "services" + + ## Credentials for basic HTTP authentication + # username = "admin" + # password = "admin" + + ## Maximum time to receive response. + # response_timeout = "5s" + + ## Optional TLS Config + # tls_ca = "/etc/telegraf/ca.pem" + # tls_cert = "/etc/telegraf/cert.pem" + # tls_key = "/etc/telegraf/key.pem" + ## Use TLS but skip chain & host verification + # insecure_skip_verify = true +``` + +### Measurements & Fields: + +- All measurements have the following fields: + - name (string) + - state_code (int) + +### Tags: + +- All measurements have the following tags: + - check_command + - display_name + - state + - source + - port + - scheme + +### Sample Queries: + +``` +SELECT * FROM "icinga2_services" WHERE state_code = 0 AND time > now() - 24h // Service with OK status +SELECT * FROM "icinga2_services" WHERE state_code = 1 AND time > now() - 24h // Service with WARNING status +SELECT * FROM "icinga2_services" WHERE state_code = 2 AND time > now() - 24h // Service with CRITICAL status +SELECT * FROM "icinga2_services" WHERE state_code = 3 AND time > now() - 24h // Service with UNKNOWN status +``` + +### Example Output: + +``` +$ ./telegraf -config telegraf.conf -input-filter icinga2 -test +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 +``` diff --git a/plugins/inputs/icinga2/icinga2.go b/plugins/inputs/icinga2/icinga2.go new file mode 100644 index 000000000..37590ab8b --- /dev/null +++ b/plugins/inputs/icinga2/icinga2.go @@ -0,0 +1,170 @@ +package icinga2 + +import ( + "encoding/json" + "fmt" + "log" + "net/http" + "net/url" + "time" + + "github.com/influxdata/telegraf" + "github.com/influxdata/telegraf/internal" + "github.com/influxdata/telegraf/internal/tls" + "github.com/influxdata/telegraf/plugins/inputs" +) + +type Icinga2 struct { + Server string + ObjectType string + Username string + Password string + ResponseTimeout internal.Duration + tls.ClientConfig + + client *http.Client +} + +type Result struct { + Results []Object `json:"results"` +} + +type Object struct { + Attrs Attribute `json:"attrs"` + Name string `json:"name"` + Joins struct{} `json:"joins"` + Meta struct{} `json:"meta"` + Type ObjectType `json:"type"` +} + +type Attribute struct { + CheckCommand string `json:"check_command"` + DisplayName string `json:"display_name"` + Name string `json:"name"` + State int `json:"state"` +} + +var levels = []string{"ok", "warning", "critical", "unknown"} + +type ObjectType string + +var sampleConfig = ` + ## Required Icinga2 server address (default: "https://localhost:5665") + # server = "https://localhost:5665" + + ## Required Icinga2 object type ("services" or "hosts, default "services") + # object_type = "services" + + ## Credentials for basic HTTP authentication + # username = "admin" + # password = "admin" + + ## Maximum time to receive response. + # response_timeout = "5s" + + ## Optional TLS Config + # tls_ca = "/etc/telegraf/ca.pem" + # tls_cert = "/etc/telegraf/cert.pem" + # tls_key = "/etc/telegraf/key.pem" + ## Use TLS but skip chain & host verification + # insecure_skip_verify = true + ` + +func (i *Icinga2) Description() string { + return "Gather Icinga2 status" +} + +func (i *Icinga2) SampleConfig() string { + return sampleConfig +} + +func (i *Icinga2) GatherStatus(acc telegraf.Accumulator, checks []Object) { + for _, check := range checks { + fields := make(map[string]interface{}) + tags := make(map[string]string) + + url, err := url.Parse(i.Server) + if err != nil { + log.Fatal(err) + } + + fields["name"] = check.Attrs.Name + fields["state_code"] = check.Attrs.State + + tags["display_name"] = check.Attrs.DisplayName + tags["check_command"] = check.Attrs.CheckCommand + tags["state"] = levels[check.Attrs.State] + tags["source"] = url.Hostname() + tags["scheme"] = url.Scheme + tags["port"] = url.Port() + + acc.AddFields(fmt.Sprintf("icinga2_%s", i.ObjectType), fields, tags) + } +} + +func (i *Icinga2) createHttpClient() (*http.Client, error) { + tlsCfg, err := i.ClientConfig.TLSConfig() + if err != nil { + return nil, err + } + + client := &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: tlsCfg, + }, + Timeout: i.ResponseTimeout.Duration, + } + + return client, nil +} + +func (i *Icinga2) Gather(acc telegraf.Accumulator) error { + if i.ResponseTimeout.Duration < time.Second { + i.ResponseTimeout.Duration = time.Second * 5 + } + + if i.client == nil { + client, err := i.createHttpClient() + if err != nil { + return err + } + i.client = client + } + + url := fmt.Sprintf("%s/v1/objects/%s?attrs=name&attrs=display_name&attrs=state&attrs=check_command", i.Server, i.ObjectType) + + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return err + } + + if i.Username != "" { + req.SetBasicAuth(i.Username, i.Password) + } + + resp, err := i.client.Do(req) + if err != nil { + return err + } + + defer resp.Body.Close() + + result := Result{} + json.NewDecoder(resp.Body).Decode(&result) + if err != nil { + return err + } + + i.GatherStatus(acc, result.Results) + + return nil +} + +func init() { + inputs.Add("icinga2", func() telegraf.Input { + return &Icinga2{ + Server: "https://localhost:5665", + ObjectType: "services", + } + }) +} diff --git a/plugins/inputs/icinga2/icinga2_test.go b/plugins/inputs/icinga2/icinga2_test.go new file mode 100644 index 000000000..ad9268347 --- /dev/null +++ b/plugins/inputs/icinga2/icinga2_test.go @@ -0,0 +1,91 @@ +package icinga2 + +import ( + "encoding/json" + "testing" + + "github.com/influxdata/telegraf/testutil" +) + +func TestGatherServicesStatus(t *testing.T) { + + s := `{"results":[ + { + "attrs": { + "check_command": "check-bgp-juniper-netconf", + "display_name": "eq-par.dc2.fr", + "name": "ef017af8-c684-4f3f-bb20-0dfe9fcd3dbe", + "state": 0 + }, + "joins": {}, + "meta": {}, + "name": "eq-par.dc2.fr!ef017af8-c684-4f3f-bb20-0dfe9fcd3dbe", + "type": "Service" + } + ]}` + + checks := Result{} + json.Unmarshal([]byte(s), &checks) + fields := map[string]interface{}{ + "name": "ef017af8-c684-4f3f-bb20-0dfe9fcd3dbe", + "state_code": 0, + } + tags := map[string]string{ + "display_name": "eq-par.dc2.fr", + "check_command": "check-bgp-juniper-netconf", + "state": "ok", + "source": "localhost", + "port": "5665", + "scheme": "https", + } + + var acc testutil.Accumulator + + icinga2 := new(Icinga2) + icinga2.ObjectType = "services" + icinga2.Server = "https://localhost:5665" + icinga2.GatherStatus(&acc, checks.Results) + acc.AssertContainsTaggedFields(t, "icinga2_services", fields, tags) +} + +func TestGatherHostsStatus(t *testing.T) { + + s := `{"results":[ + { + "attrs": { + "name": "webserver", + "address": "192.168.1.1", + "check_command": "ping", + "display_name": "apache", + "state": 2 + }, + "joins": {}, + "meta": {}, + "name": "webserver", + "type": "Host" + } + ]}` + + checks := Result{} + json.Unmarshal([]byte(s), &checks) + fields := map[string]interface{}{ + "name": "webserver", + "state_code": 2, + } + tags := map[string]string{ + "display_name": "apache", + "check_command": "ping", + "state": "critical", + "source": "localhost", + "port": "5665", + "scheme": "https", + } + + var acc testutil.Accumulator + + icinga2 := new(Icinga2) + icinga2.ObjectType = "hosts" + icinga2.Server = "https://localhost:5665" + icinga2.GatherStatus(&acc, checks.Results) + acc.AssertContainsTaggedFields(t, "icinga2_hosts", fields, tags) +}