From 6324b1fd630fb42232b82badaa05ed2acb4bbe09 Mon Sep 17 00:00:00 2001 From: Bryan FRIMIN Date: Fri, 4 Sep 2020 22:56:25 +0200 Subject: [PATCH] add nsd input plugin (#7822) --- plugins/inputs/all/all.go | 1 + plugins/inputs/nsd/README.md | 176 ++++++++++++++++++++++++ plugins/inputs/nsd/nsd.go | 167 ++++++++++++++++++++++ plugins/inputs/nsd/nsd_test.go | 244 +++++++++++++++++++++++++++++++++ 4 files changed, 588 insertions(+) create mode 100644 plugins/inputs/nsd/README.md create mode 100644 plugins/inputs/nsd/nsd.go create mode 100644 plugins/inputs/nsd/nsd_test.go diff --git a/plugins/inputs/all/all.go b/plugins/inputs/all/all.go index ba058be1c..a6ecf826f 100644 --- a/plugins/inputs/all/all.go +++ b/plugins/inputs/all/all.go @@ -111,6 +111,7 @@ import ( _ "github.com/influxdata/telegraf/plugins/inputs/nginx_sts" _ "github.com/influxdata/telegraf/plugins/inputs/nginx_upstream_check" _ "github.com/influxdata/telegraf/plugins/inputs/nginx_vts" + _ "github.com/influxdata/telegraf/plugins/inputs/nsd" _ "github.com/influxdata/telegraf/plugins/inputs/nsq" _ "github.com/influxdata/telegraf/plugins/inputs/nsq_consumer" _ "github.com/influxdata/telegraf/plugins/inputs/nstat" diff --git a/plugins/inputs/nsd/README.md b/plugins/inputs/nsd/README.md new file mode 100644 index 000000000..2d7f8833c --- /dev/null +++ b/plugins/inputs/nsd/README.md @@ -0,0 +1,176 @@ +# NSD Input Plugin + +This plugin gathers stats from +[NSD](https://www.nlnetlabs.nl/projects/nsd/about) - an authoritative DNS name +server. + +### Configuration: + +```toml +# A plugin to collect stats from the NSD DNS resolver +[[inputs.nsd]] + ## Address of server to connect to, optionally ':port'. Defaults to the + ## address in the nsd config file. + server = "127.0.0.1:8953" + + ## If running as a restricted user you can prepend sudo for additional access: + # use_sudo = false + + ## The default location of the nsd-control binary can be overridden with: + # binary = "/usr/sbin/nsd-control" + + ## The default location of the nsd config file can be overridden with: + # config_file = "/etc/nsd/nsd.conf" + + ## The default timeout of 1s can be overridden with: + # timeout = "1s" +``` + +#### Permissions: + +It's important to note that this plugin references nsd-control, which may +require additional permissions to execute successfully. Depending on the +user/group permissions of the telegraf user executing this plugin, you may +need to alter the group membership, set facls, or use sudo. + +**Group membership (Recommended)**: +```bash +$ groups telegraf +telegraf : telegraf + +$ usermod -a -G nsd telegraf + +$ groups telegraf +telegraf : telegraf nsd +``` + +**Sudo privileges**: +If you use this method, you will need the following in your telegraf config: +```toml +[[inputs.nsd]] + use_sudo = true +``` + +You will also need to update your sudoers file: +```bash +$ visudo +# Add the following line: +Cmnd_Alias NSDCONTROLCTL = /usr/sbin/nsd-control +telegraf ALL=(ALL) NOPASSWD: NSDCONTROLCTL +Defaults!NSDCONTROLCTL !logfile, !syslog, !pam_session +``` + +Please use the solution you see as most appropriate. + +### Metrics: + +This is the full list of stats provided by nsd-control. In the output, the +dots in the nsd-control stat name are replaced by underscores (see +https://www.nlnetlabs.nl/documentation/nsd/nsd-control/ for details). + +- nsd + - fields: + - num_queries + - time_boot + - time_elapsed + - size_db_disk + - size_db_mem + - size_xfrd_mem + - size_config_disk + - size_config_mem + - num_type_TYPE0 + - num_type_A + - num_type_NS + - num_type_MD + - num_type_MF + - num_type_CNAME + - num_type_SOA + - num_type_MB + - num_type_MG + - num_type_MR + - num_type_NULL + - num_type_WKS + - num_type_PTR + - num_type_HINFO + - num_type_MINFO + - num_type_MX + - num_type_TXT + - num_type_RP + - num_type_AFSDB + - num_type_X25 + - num_type_ISDN + - num_type_RT + - num_type_NSAP + - num_type_SIG + - num_type_KEY + - num_type_PX + - num_type_AAAA + - num_type_LOC + - num_type_NXT + - num_type_SRV + - num_type_NAPTR + - num_type_KX + - num_type_CERT + - num_type_DNAME + - num_type_OPT + - num_type_APL + - num_type_DS + - num_type_SSHFP + - num_type_IPSECKEY + - num_type_RRSIG + - num_type_NSEC + - num_type_DNSKEY + - num_type_DHCID + - num_type_NSEC3 + - num_type_NSEC3PARAM + - num_type_TLSA + - num_type_SMIMEA + - num_type_CDS + - num_type_CDNSKEY + - num_type_OPENPGPKEY + - num_type_CSYNC + - num_type_SPF + - num_type_NID + - num_type_L32 + - num_type_L64 + - num_type_LP + - num_type_EUI48 + - num_type_EUI64 + - num_type_TYPE252 + - num_type_TYPE253 + - num_type_TYPE255 + - num_opcode_QUERY + - num_opcode_NOTIFY + - num_class_CLASS0 + - num_class_IN + - num_class_CH + - num_rcode_NOERROR + - num_rcode_FORMERR + - num_rcode_SERVFAIL + - num_rcode_NXDOMAIN + - num_rcode_NOTIMP + - num_rcode_REFUSED + - num_rcode_YXDOMAIN + - num_rcode_NOTAUTH + - num_edns + - num_ednserr + - num_udp + - num_udp6 + - num_tcp + - num_tcp6 + - num_tls + - num_tls6 + - num_answer_wo_aa + - num_rxerr + - num_txerr + - num_raxfr + - num_truncated + - num_dropped + - zone_master + - zone_slave + +- nsd_servers + - tags: + - server + - fields: + - queries diff --git a/plugins/inputs/nsd/nsd.go b/plugins/inputs/nsd/nsd.go new file mode 100644 index 000000000..3c5d2695d --- /dev/null +++ b/plugins/inputs/nsd/nsd.go @@ -0,0 +1,167 @@ +package nsd + +import ( + "bufio" + "bytes" + "fmt" + "net" + "os/exec" + "strconv" + "strings" + "time" + + "github.com/influxdata/telegraf" + "github.com/influxdata/telegraf/filter" + "github.com/influxdata/telegraf/internal" + "github.com/influxdata/telegraf/plugins/inputs" +) + +type runner func(cmdName string, Timeout internal.Duration, UseSudo bool, Server string, ConfigFile string) (*bytes.Buffer, error) + +// NSD is used to store configuration values +type NSD struct { + Binary string + Timeout internal.Duration + UseSudo bool + Server string + ConfigFile string + + filter filter.Filter + run runner +} + +var defaultBinary = "/usr/sbin/nsd-control" +var defaultTimeout = internal.Duration{Duration: time.Second} + +var sampleConfig = ` + ## Address of server to connect to, optionally ':port'. Defaults to the + ## address in the nsd config file. + server = "127.0.0.1:8953" + + ## If running as a restricted user you can prepend sudo for additional access: + # use_sudo = false + + ## The default location of the nsd-control binary can be overridden with: + # binary = "/usr/sbin/nsd-control" + + ## The default location of the nsd config file can be overridden with: + # config_file = "/etc/nsd/nsd.conf" + + ## The default timeout of 1s can be overridden with: + # timeout = "1s" +` + +// Description displays what this plugin is about +func (s *NSD) Description() string { + return "A plugin to collect stats from the NSD authoritative DNS name server" +} + +// SampleConfig displays configuration instructions +func (s *NSD) SampleConfig() string { + return sampleConfig +} + +// Shell out to nsd_stat and return the output +func nsdRunner(cmdName string, Timeout internal.Duration, UseSudo bool, Server string, ConfigFile string) (*bytes.Buffer, error) { + cmdArgs := []string{"stats_noreset"} + + if Server != "" { + host, port, err := net.SplitHostPort(Server) + if err == nil { + Server = host + "@" + port + } + + cmdArgs = append([]string{"-s", Server}, cmdArgs...) + } + + if ConfigFile != "" { + cmdArgs = append([]string{"-c", ConfigFile}, cmdArgs...) + } + + cmd := exec.Command(cmdName, cmdArgs...) + + if UseSudo { + cmdArgs = append([]string{cmdName}, cmdArgs...) + cmd = exec.Command("sudo", cmdArgs...) + } + + var out bytes.Buffer + cmd.Stdout = &out + err := internal.RunTimeout(cmd, Timeout.Duration) + if err != nil { + return &out, fmt.Errorf("error running nsd-control: %s (%s %v)", err, cmdName, cmdArgs) + } + + return &out, nil +} + +// Gather collects stats from nsd-control and adds them to the Accumulator +func (s *NSD) Gather(acc telegraf.Accumulator) error { + out, err := s.run(s.Binary, s.Timeout, s.UseSudo, s.Server, s.ConfigFile) + if err != nil { + return fmt.Errorf("error gathering metrics: %s", err) + } + + // Process values + fields := make(map[string]interface{}) + fieldsServers := make(map[string]map[string]interface{}) + + scanner := bufio.NewScanner(out) + for scanner.Scan() { + cols := strings.Split(scanner.Text(), "=") + + // Check split correctness + if len(cols) != 2 { + continue + } + + stat := cols[0] + value := cols[1] + + fieldValue, err := strconv.ParseFloat(value, 64) + if err != nil { + acc.AddError(fmt.Errorf("Expected a numerical value for %s = %v", + stat, value)) + continue + } + + if strings.HasPrefix(stat, "server") { + statTokens := strings.Split(stat, ".") + if len(statTokens) > 1 { + serverId := strings.TrimPrefix(statTokens[0], "server") + if _, err := strconv.Atoi(serverId); err == nil { + serverTokens := statTokens[1:] + field := strings.Join(serverTokens[:], "_") + if fieldsServers[serverId] == nil { + fieldsServers[serverId] = make(map[string]interface{}) + } + fieldsServers[serverId][field] = fieldValue + } + } + } else { + field := strings.Replace(stat, ".", "_", -1) + fields[field] = fieldValue + } + } + + acc.AddFields("nsd", fields, nil) + for thisServerId, thisServerFields := range fieldsServers { + thisServerTag := map[string]string{"server": thisServerId} + acc.AddFields("nsd_servers", thisServerFields, thisServerTag) + } + + return nil +} + +func init() { + inputs.Add("nsd", func() telegraf.Input { + return &NSD{ + run: nsdRunner, + Binary: defaultBinary, + Timeout: defaultTimeout, + UseSudo: false, + Server: "", + ConfigFile: "", + } + }) +} diff --git a/plugins/inputs/nsd/nsd_test.go b/plugins/inputs/nsd/nsd_test.go new file mode 100644 index 000000000..ee527f7b7 --- /dev/null +++ b/plugins/inputs/nsd/nsd_test.go @@ -0,0 +1,244 @@ +package nsd + +import ( + "bytes" + "testing" + "time" + + "github.com/influxdata/telegraf/internal" + "github.com/influxdata/telegraf/testutil" + "github.com/stretchr/testify/assert" +) + +var TestTimeout = internal.Duration{Duration: time.Second} + +func NSDControl(output string, Timeout internal.Duration, useSudo bool, Server string, ConfigFile string) func(string, internal.Duration, bool, string, string) (*bytes.Buffer, error) { + return func(string, internal.Duration, bool, string, string) (*bytes.Buffer, error) { + return bytes.NewBuffer([]byte(output)), nil + } +} + +func TestParseFullOutput(t *testing.T) { + acc := &testutil.Accumulator{} + v := &NSD{ + run: NSDControl(fullOutput, TestTimeout, true, "", ""), + } + err := v.Gather(acc) + + assert.NoError(t, err) + + assert.True(t, acc.HasMeasurement("nsd")) + assert.True(t, acc.HasMeasurement("nsd_servers")) + + assert.Len(t, acc.Metrics, 2) + assert.Equal(t, 99, acc.NFields()) + + acc.AssertContainsFields(t, "nsd", parsedFullOutput) + acc.AssertContainsFields(t, "nsd_servers", parsedFullOutputServerAsTag) + +} + +var parsedFullOutputServerAsTag = map[string]interface{}{ + "queries": float64(75576), +} + +var parsedFullOutput = map[string]interface{}{ + "num_queries": float64(75557), + "time_boot": float64(2944405.500253), + "time_elapsed": float64(2944405.500253), + "size_db_disk": float64(98304), + "size_db_mem": float64(22784), + "size_xfrd_mem": float64(83956312), + "size_config_disk": float64(0), + "size_config_mem": float64(6088), + "num_type_TYPE0": float64(6), + "num_type_A": float64(46311), + "num_type_NS": float64(478), + "num_type_MD": float64(0), + "num_type_MF": float64(0), + "num_type_CNAME": float64(272), + "num_type_SOA": float64(596), + "num_type_MB": float64(0), + "num_type_MG": float64(0), + "num_type_MR": float64(0), + "num_type_NULL": float64(0), + "num_type_WKS": float64(0), + "num_type_PTR": float64(83), + "num_type_HINFO": float64(1), + "num_type_MINFO": float64(0), + "num_type_MX": float64(296), + "num_type_TXT": float64(794), + "num_type_RP": float64(0), + "num_type_AFSDB": float64(0), + "num_type_X25": float64(0), + "num_type_ISDN": float64(0), + "num_type_RT": float64(0), + "num_type_NSAP": float64(0), + "num_type_SIG": float64(0), + "num_type_KEY": float64(1), + "num_type_PX": float64(0), + "num_type_AAAA": float64(22736), + "num_type_LOC": float64(2), + "num_type_NXT": float64(0), + "num_type_SRV": float64(93), + "num_type_NAPTR": float64(5), + "num_type_KX": float64(0), + "num_type_CERT": float64(0), + "num_type_DNAME": float64(0), + "num_type_OPT": float64(0), + "num_type_APL": float64(0), + "num_type_DS": float64(0), + "num_type_SSHFP": float64(0), + "num_type_IPSECKEY": float64(0), + "num_type_RRSIG": float64(21), + "num_type_NSEC": float64(0), + "num_type_DNSKEY": float64(325), + "num_type_DHCID": float64(0), + "num_type_NSEC3": float64(0), + "num_type_NSEC3PARAM": float64(0), + "num_type_TLSA": float64(35), + "num_type_SMIMEA": float64(0), + "num_type_CDS": float64(0), + "num_type_CDNSKEY": float64(0), + "num_type_OPENPGPKEY": float64(0), + "num_type_CSYNC": float64(0), + "num_type_SPF": float64(16), + "num_type_NID": float64(0), + "num_type_L32": float64(0), + "num_type_L64": float64(0), + "num_type_LP": float64(0), + "num_type_EUI48": float64(0), + "num_type_EUI64": float64(0), + "num_type_TYPE252": float64(962), + "num_type_TYPE253": float64(2), + "num_type_TYPE255": float64(1840), + "num_opcode_QUERY": float64(75527), + "num_opcode_NOTIFY": float64(6), + "num_class_CLASS0": float64(6), + "num_class_IN": float64(75395), + "num_class_CH": float64(132), + "num_rcode_NOERROR": float64(65541), + "num_rcode_FORMERR": float64(8), + "num_rcode_SERVFAIL": float64(0), + "num_rcode_NXDOMAIN": float64(6642), + "num_rcode_NOTIMP": float64(18), + "num_rcode_REFUSED": float64(3341), + "num_rcode_YXDOMAIN": float64(0), + "num_rcode_NOTAUTH": float64(2), + "num_edns": float64(71398), + "num_ednserr": float64(0), + "num_udp": float64(34111), + "num_udp6": float64(40429), + "num_tcp": float64(1015), + "num_tcp6": float64(2), + "num_tls": float64(0), + "num_tls6": float64(0), + "num_answer_wo_aa": float64(13), + "num_rxerr": float64(0), + "num_txerr": float64(0), + "num_raxfr": float64(954), + "num_truncated": float64(1), + "num_dropped": float64(5), + "zone_master": float64(2), + "zone_slave": float64(1), +} + +var fullOutput = `server0.queries=75576 +num.queries=75557 +time.boot=2944405.500253 +time.elapsed=2944405.500253 +size.db.disk=98304 +size.db.mem=22784 +size.xfrd.mem=83956312 +size.config.disk=0 +size.config.mem=6088 +num.type.TYPE0=6 +num.type.A=46311 +num.type.NS=478 +num.type.MD=0 +num.type.MF=0 +num.type.CNAME=272 +num.type.SOA=596 +num.type.MB=0 +num.type.MG=0 +num.type.MR=0 +num.type.NULL=0 +num.type.WKS=0 +num.type.PTR=83 +num.type.HINFO=1 +num.type.MINFO=0 +num.type.MX=296 +num.type.TXT=794 +num.type.RP=0 +num.type.AFSDB=0 +num.type.X25=0 +num.type.ISDN=0 +num.type.RT=0 +num.type.NSAP=0 +num.type.SIG=0 +num.type.KEY=1 +num.type.PX=0 +num.type.AAAA=22736 +num.type.LOC=2 +num.type.NXT=0 +num.type.SRV=93 +num.type.NAPTR=5 +num.type.KX=0 +num.type.CERT=0 +num.type.DNAME=0 +num.type.OPT=0 +num.type.APL=0 +num.type.DS=0 +num.type.SSHFP=0 +num.type.IPSECKEY=0 +num.type.RRSIG=21 +num.type.NSEC=0 +num.type.DNSKEY=325 +num.type.DHCID=0 +num.type.NSEC3=0 +num.type.NSEC3PARAM=0 +num.type.TLSA=35 +num.type.SMIMEA=0 +num.type.CDS=0 +num.type.CDNSKEY=0 +num.type.OPENPGPKEY=0 +num.type.CSYNC=0 +num.type.SPF=16 +num.type.NID=0 +num.type.L32=0 +num.type.L64=0 +num.type.LP=0 +num.type.EUI48=0 +num.type.EUI64=0 +num.type.TYPE252=962 +num.type.TYPE253=2 +num.type.TYPE255=1840 +num.opcode.QUERY=75527 +num.opcode.NOTIFY=6 +num.class.CLASS0=6 +num.class.IN=75395 +num.class.CH=132 +num.rcode.NOERROR=65541 +num.rcode.FORMERR=8 +num.rcode.SERVFAIL=0 +num.rcode.NXDOMAIN=6642 +num.rcode.NOTIMP=18 +num.rcode.REFUSED=3341 +num.rcode.YXDOMAIN=0 +num.rcode.NOTAUTH=2 +num.edns=71398 +num.ednserr=0 +num.udp=34111 +num.udp6=40429 +num.tcp=1015 +num.tcp6=2 +num.tls=0 +num.tls6=0 +num.answer_wo_aa=13 +num.rxerr=0 +num.txerr=0 +num.raxfr=954 +num.truncated=1 +num.dropped=5 +zone.master=2 +zone.slave=1`