diff options
author | Eriks Sneiders <eriks.sneiders@zabbix.com> | 2022-03-02 12:58:26 +0300 |
---|---|---|
committer | Eriks Sneiders <eriks.sneiders@zabbix.com> | 2022-03-02 12:58:26 +0300 |
commit | 7fcf587d3377bd91bd088de55f10b4432b0081da (patch) | |
tree | 9d014b3afb694b66364532a4940b71ffdd7f334c /src/go/plugins | |
parent | ca0831434ec0076b1c4bc9ac24da77e6c7024cb2 (diff) | |
parent | 773b472ca083c6c4952acafee4ecf6ab1253b68e (diff) |
.......... [ZBXNEXT-7065] updating branch to current release/6.0
Diffstat (limited to 'src/go/plugins')
-rw-r--r-- | src/go/plugins/net/dns/dns.go | 502 | ||||
-rw-r--r-- | src/go/plugins/net/dns/dns_nix.go | 44 | ||||
-rw-r--r-- | src/go/plugins/net/dns/dns_windows.go | 79 | ||||
-rw-r--r-- | src/go/plugins/plugins_linux.go | 1 | ||||
-rw-r--r-- | src/go/plugins/plugins_windows.go | 1 | ||||
-rw-r--r-- | src/go/plugins/smart/smart.go | 125 | ||||
-rw-r--r-- | src/go/plugins/smart/smart_test.go | 184 | ||||
-rw-r--r-- | src/go/plugins/smart/smartfs.go | 28 | ||||
-rw-r--r-- | src/go/plugins/system/cpu/cpu.go | 18 | ||||
-rw-r--r-- | src/go/plugins/system/cpu/cpu_linux.go | 29 | ||||
-rw-r--r-- | src/go/plugins/system/cpu/cpu_linux_test.go | 136 | ||||
-rw-r--r-- | src/go/plugins/system/cpu/cpu_windows.go | 9 | ||||
-rw-r--r-- | src/go/plugins/systemd/systemd.go | 42 | ||||
-rw-r--r-- | src/go/plugins/zabbix/sync/sync_nix.go | 3 | ||||
-rw-r--r-- | src/go/plugins/zabbix/sync/sync_windows.go | 2 |
15 files changed, 1134 insertions, 69 deletions
diff --git a/src/go/plugins/net/dns/dns.go b/src/go/plugins/net/dns/dns.go new file mode 100644 index 00000000000..306b842adc0 --- /dev/null +++ b/src/go/plugins/net/dns/dns.go @@ -0,0 +1,502 @@ +/* +** Zabbix +** Copyright (C) 2001-2021 Zabbix SIA +** +** This program is free software; you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation; either version 2 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program; if not, write to the Free Software +** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +**/ + +package dns + +import ( + "fmt" + "net" + "strconv" + "strings" + "time" + + "github.com/miekg/dns" + "zabbix.com/pkg/plugin" + "zabbix.com/pkg/zbxerr" +) + +const ( + base10 = 10 + + tcpProtocol = "tcp" + udpProtocol = "udp" +) + +const ( + noneParam = iota + firstParam + secondParam + thirdParam + fourthParam + fifthParam + sixthParam +) + +type options struct { + ip string + name string + protocol string + dnsType uint16 + count int + timeout time.Duration +} + +// Plugin - +type Plugin struct { + plugin.Base +} + +var impl Plugin + +var dnsTypes = map[string]uint16{ + "ANY": dns.TypeANY, + "A": dns.TypeA, + "NS": dns.TypeNS, + "CNAME": dns.TypeCNAME, + "MB": dns.TypeMB, + "MG": dns.TypeMG, + "MR": dns.TypeMR, + "PTR": dns.TypePTR, + "MD": dns.TypeMD, + "MF": dns.TypeMF, + "MX": dns.TypeMX, + "SOA": dns.TypeSOA, + "NULL": dns.TypeNULL, + "HINFO": dns.TypeHINFO, + "MINFO": dns.TypeMINFO, + "TXT": dns.TypeTXT, + "AAAA": dns.TypeAAAA, + "SRV": dns.TypeSRV, +} + +// Export - +func (p *Plugin) Export(key string, params []string, ctx plugin.ContextProvider) (result interface{}, err error) { + switch key { + case "net.dns": + return exportDns(params) + case "net.dns.record": + return exportDnsRecord(params) + default: + err = zbxerr.ErrorUnsupportedMetric + + return + } +} + +func exportDns(params []string) (result interface{}, err error) { + answer, err := getDNSAnswers(params) + if err != nil { + return + } + + if len(answer) < 1 { + return 0, nil + } + + return 1, nil +} + +func exportDnsRecord(params []string) (result interface{}, err error) { + answer, err := getDNSAnswers(params) + if err != nil { + return + } + + if len(answer) < 1 { + return nil, zbxerr.New("Cannot perform DNS query.") + } + + return parseAnswers(answer), nil +} + +func parseAnswers(answers []dns.RR) string { + var out string + answersNum := len(answers) + for i, a := range answers { + out += fmt.Sprintf("%-20s", strings.TrimSuffix(a.Header().Name, ".")) + out += fmt.Sprintf(" %-8s ", dns.Type(a.Header().Rrtype).String()) + + switch rr := a.(type) { + case *dns.A: + out += getAString(rr) + case *dns.NS: + out += getNSString(rr) + case *dns.CNAME: + out += getCNAMEString(rr) + case *dns.MB: + out += getMBString(rr) + case *dns.MG: + out += getMGString(rr) + case *dns.PTR: + out += getPTRString(rr) + case *dns.MD: + out += getMDString(rr) + case *dns.MF: + out += getMFString(rr) + case *dns.MX: + out += getMXString(rr) + case *dns.SOA: + out += getSOAString(rr) + case *dns.NULL: + out += getNULLString(rr) + case *dns.HINFO: + out += getHINFOString(rr) + case *dns.MINFO: + out += getMINFOString(rr) + case *dns.TXT: + out += getTXTString(rr) + case *dns.AAAA: + out += getAAAAString(rr) + case *dns.SRV: + out += getSRVString(rr) + } + + if i != answersNum - 1 { + out += "\n" + } + } + + return out +} + +func getDNSAnswers(params []string) ([]dns.RR, error) { + options, err := parseParamas(params) + if err != nil { + return nil, err + } + + var resp *dns.Msg + for i := 1; i <= options.count; i++ { + resp, err = runQuery(options.ip, options.name, options.protocol, options.dnsType, options.timeout) + if err != nil { + continue + } + + break + } + + if err != nil { + return nil, zbxerr.ErrorCannotFetchData.Wrap(err) + } + + return resp.Answer, nil +} + +func getSOAString(in *dns.SOA) string { + return strings.TrimSuffix(in.Ns, ".") + + " " + strings.TrimSuffix(in.Mbox, ".") + + " " + strconv.FormatInt(int64(in.Serial), base10) + + " " + strconv.FormatInt(int64(in.Refresh), base10) + + " " + strconv.FormatInt(int64(in.Retry), base10) + + " " + strconv.FormatInt(int64(in.Expire), base10) + + " " + strconv.FormatInt(int64(in.Minttl), base10) +} + +func getAString(in *dns.A) string { + if in.A == nil { + return "\n" + } + + return in.A.String() +} + +func getNSString(in *dns.NS) string { + return strings.TrimSuffix(in.Ns, ".") +} + +func getCNAMEString(in *dns.CNAME) string { + return strings.TrimSuffix(in.Target, ".") +} + +func getMBString(in *dns.MB) string { + return strings.TrimSuffix(in.Mb, ".") +} + +func getMGString(in *dns.MG) string { + return strings.TrimSuffix(in.Mg, ".") +} + +func getPTRString(in *dns.PTR) string { + return strings.TrimSuffix(in.Ptr, ".") +} + +func getMDString(in *dns.MD) string { + return strings.TrimSuffix(in.Md, ".") +} + +func getMFString(in *dns.MF) string { + return strings.TrimSuffix(in.Mf, ".") +} + +func getMXString(in *dns.MX) string { + return strconv.Itoa(int(in.Preference)) + + " " + strings.TrimSuffix(in.Mx, ".") +} + +func getNULLString(in *dns.NULL) string { + return strings.TrimSuffix(in.Data, ".") +} + +func getHINFOString(in *dns.HINFO) string { + return parseTXT(in.Cpu, in.Os) + +} + +func getMINFOString(in *dns.MINFO) string { + return strings.TrimSuffix(in.Rmail, ".") + " " + + strings.TrimSuffix(in.Email, ".") +} + +func getTXTString(in *dns.TXT) string { + return parseTXT(in.Txt...) +} + +func getAAAAString(in *dns.AAAA) string { + if in.AAAA == nil { + return "\n" + } + + return in.AAAA.String() +} + +func getSRVString(in *dns.SRV) string { + return strconv.Itoa(int(in.Priority)) + " " + + strconv.Itoa(int(in.Weight)) + " " + + strconv.Itoa(int(in.Port)) + " " + + strings.TrimSuffix(in.Target, ".") +} + +func parseTXT(in ...string) string { + var out string + for _, s := range in { + if s != "" { + out += "\"" + s + "\"" + " " + } + } + + return strings.TrimSpace(out) +} + +func parseParamas(params []string) (o options, err error) { + switch len(params) { + case sixthParam: + err = o.setProtocol(params[sixthParam-1]) + if err != nil { + return + } + + fallthrough + case fifthParam: + err = o.setCount(params[fifthParam-1]) + if err != nil { + return + } + + fallthrough + case fourthParam: + err = o.setTimeout(params[fourthParam-1]) + if err != nil { + return + } + + fallthrough + case thirdParam: + err = o.setDNSType(params[thirdParam-1]) + if err != nil { + return + } + + fallthrough + case secondParam: + o.name = params[secondParam-1] + + fallthrough + case firstParam: + err = o.setIP(params[firstParam-1]) + if err != nil { + return o, zbxerr.New(fmt.Sprintf("invalid fist parameter, %s", err.Error())) + } + + fallthrough + case noneParam: + err = o.setDefaults() + if err != nil { + return + } + default: + err = zbxerr.ErrorTooManyParameters + + return + } + + return +} + +func (o *options) setIP(ip string) error { + if ip == "" { + return nil + } + + if !isValidIP(ip) { + return fmt.Errorf("invalid IP address, %s", ip) + } + + o.ip = net.JoinHostPort(ip, "53") + + return nil +} + +func isValidIP(ip string) bool { + if r := net.ParseIP(ip); r == nil { + return false + } + + return true +} + +func (o *options) setProtocol(protocol string) error { + switch protocol { + case tcpProtocol: + o.protocol = tcpProtocol + case udpProtocol, "": + o.protocol = udpProtocol + default: + return zbxerr.New("invalid sixth parameter") + } + + return nil +} + +func (o *options) setCount(c string) error { + if c == "" { + return nil + } + + count, err := strconv.Atoi(c) + if err != nil { + return zbxerr.New(fmt.Sprintf("invalid fifth parameter, %s", err.Error())) + } + + if count <= 0 { + return zbxerr.New("invalid fifth parameter") + } + + o.count = count + + return nil +} + +func (o *options) setTimeout(timeout string) error { + if timeout == "" { + return nil + } + + t, err := strconv.Atoi(timeout) + if err != nil { + return zbxerr.New(fmt.Sprintf("invalid fourth parameter, %s", err.Error())) + } + + if t <= 0 { + return zbxerr.New("invalid fourth parameter") + } + + o.timeout = time.Duration(t) * time.Second + + return nil +} + +func (o *options) setDNSType(dnsType string) error { + if dnsType == "" { + return nil + } + + t, ok := dnsTypes[strings.ToUpper(dnsType)] + if !ok { + return zbxerr.New(fmt.Sprintf("invalid third parameter, unknown dns type %s", dnsType)) + } + + o.dnsType = t + + return nil +} + +func (o *options) setDefaults() error { + if o.ip == "" { + err := o.setDefaultIP() + if err != nil { + return zbxerr.New(err.Error()) + } + } + + if o.name == "" { + o.setDefaultName() + } + + if o.dnsType == dns.TypeNone { + o.dnsType = dns.TypeSOA + } + + if o.timeout < 1 { + o.timeout = 1 * time.Second + } + + if o.count < 1 { + o.count = 2 + } + + if o.protocol == "" { + o.protocol = udpProtocol + } + + return nil +} + +func (o *options) setDefaultName() { + o.name = "zabbix.com" +} + +func runQuery(resolver, domain, net string, record uint16, timeout time.Duration) (*dns.Msg, error) { + c := new(dns.Client) + c.Net = net + c.DialTimeout = timeout + c.ReadTimeout = timeout + c.WriteTimeout = timeout + + m := &dns.Msg{ + MsgHdr: dns.MsgHdr{ + CheckingDisabled: false, + RecursionDesired: true, + Opcode: dns.OpcodeQuery, + Rcode: dns.RcodeSuccess, + }, + Question: make([]dns.Question, 1), + } + + m.Question[0] = dns.Question{Name: dns.Fqdn(domain), Qtype: record, Qclass: dns.ClassINET} + r, _, err := c.Exchange(m, resolver) + + return r, err +} + +func init() { + plugin.RegisterMetrics(&impl, "DNS", + "net.dns", "Checks if DNS service is up.", + "net.dns.record", "Performs a DNS query.", + ) +} diff --git a/src/go/plugins/net/dns/dns_nix.go b/src/go/plugins/net/dns/dns_nix.go new file mode 100644 index 00000000000..8c617e17d34 --- /dev/null +++ b/src/go/plugins/net/dns/dns_nix.go @@ -0,0 +1,44 @@ +// +build !windows + +/* +** Zabbix +** Copyright (C) 2001-2021 Zabbix SIA +** +** This program is free software; you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation; either version 2 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program; if not, write to the Free Software +** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +**/ + +package dns + +import ( + "fmt" + "os" + "strings" +) + +func (o *options) setDefaultIP() (err error) { + data, err := os.ReadFile("/etc/resolv.conf") + if err != nil { + return + } + + s := strings.Split(string(data), "\n") + for _, tmp := range s { + if strings.HasPrefix(tmp, "nameserver") { + return o.setIP(strings.TrimSpace(strings.TrimPrefix(tmp, "nameserver"))) + } + } + + return fmt.Errorf("cannot find default dns nameserver") +} diff --git a/src/go/plugins/net/dns/dns_windows.go b/src/go/plugins/net/dns/dns_windows.go new file mode 100644 index 00000000000..baf45c3d40d --- /dev/null +++ b/src/go/plugins/net/dns/dns_windows.go @@ -0,0 +1,79 @@ +/* +** Zabbix +** Copyright (C) 2001-2021 Zabbix SIA +** +** This program is free software; you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation; either version 2 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program; if not, write to the Free Software +** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +**/ + +package dns + +import ( + "strings" + "unsafe" + + "golang.org/x/sys/windows" + "zabbix.com/pkg/zbxerr" +) + +func (o *options) setDefaultIP() error { + l := uint32(20000) + b := make([]byte, l) + + if err := windows.GetAdaptersAddresses(windows.AF_UNSPEC, windows.GAA_FLAG_INCLUDE_PREFIX, 0, (*windows.IpAdapterAddresses)(unsafe.Pointer(&b[0])), &l); err != nil { + return err + } + + var addresses []*windows.IpAdapterAddresses + for addr := (*windows.IpAdapterAddresses)(unsafe.Pointer(&b[0])); addr != nil; addr = addr.Next { + addresses = append(addresses, addr) + } + + resolvers := map[string]bool{} + for _, addr := range addresses { + for next := addr.FirstUnicastAddress; next != nil; next = next.Next { + if addr.OperStatus != windows.IfOperStatusUp { + continue + } + + if next.Address.IP() != nil { + for dnsServer := addr.FirstDnsServerAddress; dnsServer != nil; dnsServer = dnsServer.Next { + ip := dnsServer.Address.IP() + if ip.IsMulticast() || ip.IsLinkLocalMulticast() || ip.IsLinkLocalUnicast() || ip.IsUnspecified() { + continue + } + + if ip.To16() != nil && strings.HasPrefix(ip.To16().String(), "fec0:") { + continue + } + + resolvers[ip.String()] = true + } + + break + } + } + } + + servers := []string{} + for server := range resolvers { + servers = append(servers, server) + } + + if len(servers) < 0 { + return zbxerr.New("no dns server found") + } + + return o.setIP(servers[0]) +} diff --git a/src/go/plugins/plugins_linux.go b/src/go/plugins/plugins_linux.go index 33788d49989..d0e6fd33620 100644 --- a/src/go/plugins/plugins_linux.go +++ b/src/go/plugins/plugins_linux.go @@ -29,6 +29,7 @@ import ( _ "zabbix.com/plugins/mongodb" _ "zabbix.com/plugins/mqtt" _ "zabbix.com/plugins/mysql" + _ "zabbix.com/plugins/net/dns" _ "zabbix.com/plugins/net/netif" _ "zabbix.com/plugins/net/tcp" _ "zabbix.com/plugins/net/udp" diff --git a/src/go/plugins/plugins_windows.go b/src/go/plugins/plugins_windows.go index 0c5c15c0dc8..95b9060aaae 100644 --- a/src/go/plugins/plugins_windows.go +++ b/src/go/plugins/plugins_windows.go @@ -27,6 +27,7 @@ import ( _ "zabbix.com/plugins/mongodb" _ "zabbix.com/plugins/mqtt" _ "zabbix.com/plugins/mysql" + _ "zabbix.com/plugins/net/dns" _ "zabbix.com/plugins/net/netif" _ "zabbix.com/plugins/net/tcp" _ "zabbix.com/plugins/net/udp" diff --git a/src/go/plugins/smart/smart.go b/src/go/plugins/smart/smart.go index a1a1fce7eef..f71407b6b97 100644 --- a/src/go/plugins/smart/smart.go +++ b/src/go/plugins/smart/smart.go @@ -21,7 +21,6 @@ package smart import ( "encoding/json" - "strings" "zabbix.com/pkg/conf" "zabbix.com/pkg/plugin" @@ -84,7 +83,7 @@ func (p *Plugin) Export(key string, params []string, ctx plugin.ContextProvider) for _, dev := range r.devices { out = append(out, device{ Name: cutPrefix(dev.Info.Name), - DeviceType: strings.ToUpper(getType(dev.Info.DevType, dev.RotationRate)), + DeviceType: getType(dev.Info.DevType, dev.RotationRate, dev.SmartAttributes.Table), Model: dev.ModelName, SerialNumber: dev.SerialNumber, }) } @@ -128,13 +127,7 @@ func (p *Plugin) Export(key string, params []string, ctx plugin.ContextProvider) } for _, dev := range r.devices { - var t string - if dev.RotationRate == 0 { - t = "SSD" - } else { - t = "HDD" - } - + t := getAttributeType(dev.Info.DevType, dev.RotationRate, dev.SmartAttributes.Table) for _, attr := range dev.SmartAttributes.Table { out = append( out, attribute{ @@ -170,52 +163,108 @@ func setDiskFields(deviceJsons map[string]jsonDevice) (out []interface{}, err er } b["disk_name"] = cutPrefix(k) + b["disk_type"] = getType(getTypeFromJson(b), getRateFromJson(b), getTablesFromJson(b)) - var devType string + out = append(out, b) + } - if dev, ok := b["device"]; ok { - s, ok := dev.(string) - if ok { - info := make(map[string]string) - if err = json.Unmarshal([]byte(s), &info); err != nil { - return out, zbxerr.ErrorCannotUnmarshalJSON.Wrap(err) - } + return +} - devType = info["type"] - } +func getRateFromJson(in map[string]interface{}) (out int) { + if r, ok := in[rotationRateFieldName]; ok { + switch rate := r.(type) { + case int: + out = rate + case float64: + out = int(rate) } + } - rateInt := -1 + return +} - if rate, ok := b["rotation_rate"]; ok { - switch r := rate.(type) { - case int: - rateInt = r - case float64: - rateInt = int(r) +func getTypeFromJson(in map[string]interface{}) (out string) { + if dev, ok := in[deviceFieldName]; ok { + m, ok := dev.(map[string]interface{}) + if ok { + if t, ok := m[typeFieldName]; ok { + s, ok := t.(string) + if ok { + out = s + } } } + } - b["disk_type"] = getType(devType, rateInt) - out = append(out, b) + return +} + +func getTablesFromJson(in map[string]interface{}) (out []table) { + attr, ok := in[ataSmartAttrFieldName] + if !ok { + return + } + + a, ok := attr.(map[string]interface{}) + if !ok { + return + } + + tables, ok := a[ataSmartAttrTableFieldName] + if !ok { + return + } + + tmp, ok := tables.([]interface{}) + if !ok { + return + } + + b, err := json.Marshal(tmp) + if err != nil { + return + } + + err = json.Unmarshal(b, &out) + if err != nil { + return } return } -func getType(devType string, rate int) (out string) { - out = "unknown" - if devType == "nvme" { - out = "nvme" - } else { - if rate == 0 { - out = "ssd" - } else if rate > 0 { - out = "hdd" +func getAttributeType(devType string, rate int, tables []table) string { + if devType == unknownType { + return unknownType + } + + return getTypeByRateAndAttr(rate, tables) +} + +func getType(devType string, rate int, tables []table) string { + switch devType { + case nvmeType: + return nvmeType + case unknownType: + return unknownType + default: + return getTypeByRateAndAttr(rate, tables) + } +} + +func getTypeByRateAndAttr(rate int, tables []table) string { + if rate > 0 { + return hddType + } + + for _, t := range tables { + if t.Attrname == spinUpAttrName { + return hddType } } - return + return ssdType } func init() { diff --git a/src/go/plugins/smart/smart_test.go b/src/go/plugins/smart/smart_test.go index 1ca089c02d5..eb73c16c8c6 100644 --- a/src/go/plugins/smart/smart_test.go +++ b/src/go/plugins/smart/smart_test.go @@ -21,9 +21,18 @@ package smart import ( "fmt" + "reflect" "testing" ) +var ( + table1 = table{"test1", 1, 11} + table2 = table{"test2", 2, 22} + table3 = table{"test3", 3, 33} + table4 = table{"test4", 4, 44} + attrTable = table{"Spin_Up_Time", 5, 55} +) + func Test_setDiskFields(t *testing.T) { //nolint:lll jsonSdaStr := `{"device": {"name": "/dev/sda","info_name": "/dev/sda [SAT]","type": "sat","protocol": "ATA"},"rotation_rate": 0}` @@ -63,27 +72,192 @@ func Test_setDiskFields(t *testing.T) { } } +func Test_getRateFromJson(t *testing.T) { + type args struct { + in map[string]interface{} + } + tests := []struct { + name string + args args + wantOut int + }{ + {"rate", args{map[string]interface{}{"rotation_rate": 10}}, 10}, + {"multiple_fields", args{map[string]interface{}{"foobar": "abc", "rotation_rate": 10}}, 10}, + {"no_rate", args{map[string]interface{}{"foobar": "abc"}}, 0}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if gotOut := getRateFromJson(tt.args.in); gotOut != tt.wantOut { + t.Errorf("getRateFromJson() = %v, want %v", gotOut, tt.wantOut) + } + }) + } +} + +func Test_getTypeFromJson(t *testing.T) { + map1 := make(map[string]interface{}) + map1["device"] = map[string]interface{}{"type": "sat"} + + map2 := make(map[string]interface{}) + map2["device"] = map[string]interface{}{"type": "sat", "foobar": "abc"} + + map3 := make(map[string]interface{}) + map3["device"] = map[string]interface{}{"foobar": "abc"} + + type args struct { + in map[string]interface{} + } + tests := []struct { + name string + args args + wantOut string + }{ + {"type", args{map1}, "sat"}, + {"multiple_fields", args{map2}, "sat"}, + {"no_type", args{map3}, ""}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if gotOut := getTypeFromJson(tt.args.in); gotOut != tt.wantOut { + t.Errorf("getTypeFromJson() = %v, want %v", gotOut, tt.wantOut) + } + }) + } +} + +func Test_getTablesFromJson(t *testing.T) { + map1 := make(map[string]interface{}) + map1["table"] = []interface{}{table1, table2, attrTable} + + map2 := make(map[string]interface{}) + map2["table"] = []interface{}{table1, table2, table4} + + attrTable1 := map[string]interface{}{"ata_smart_attributes": map1} + attrTable2 := map[string]interface{}{"ata_smart_attributes": map2} + attrTable3 := map[string]interface{}{"ata_smart_attributes": nil} + attrTable4 := map[string]interface{}{"ata_smart_attributes": []table{}} + attrTable5 := map[string]interface{}{"ata_smart_attributes": map[string][]table{}} + + type args struct { + in map[string]interface{} + } + tests := []struct { + name string + args args + want []table + }{ + {"attr_table", args{attrTable1}, []table{table1, table2, attrTable}}, + {"no_attr_table", args{attrTable2}, []table{table1, table2, table4}}, + {"no_table", args{attrTable3}, nil}, + {"incorrect_table_value", args{attrTable4}, nil}, + {"empty_map", args{attrTable5}, nil}, + {"no_ata_attributes", args{nil}, nil}, + {"empty_ata_attributes", args{map[string]interface{}{}}, nil}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := getTablesFromJson(tt.args.in); !reflect.DeepEqual(got, tt.want) { + t.Errorf("getTablesFromJson() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_getAttributeType(t *testing.T) { + type args struct { + devType string + rate int + tables []table + } + tests := []struct { + name string + args args + want string + }{ + {"ssd_no_tables", args{"SAT", 0, nil}, "ssd"}, + {"ssd_tables_no_spin_up_table", args{"SAT", 0, []table{table1, table2, table4}}, "ssd"}, + {"hdd_no_tables", args{"SAT", 12, nil}, "hdd"}, + {"hdd_rate_spin_up_table", args{"SAT", 12, []table{table1, table2, table4, attrTable}}, "hdd"}, + {"hdd_no_rate_spin_up_table", args{"SAT", 0, []table{table1, table2, table4, attrTable}}, "hdd"}, + {"hdd_no_spin_up_table", args{"SAT", 12, []table{table1, table2, table4}}, "hdd"}, + {"unknown_no_attr_table", args{"unknown", 1000, []table{table1, table2, table4}}, "unknown"}, + {"unknown_value_table", args{"unknown", 1000, []table{table1, table2, table4, attrTable}}, "unknown"}, + {"unknown_no_rate_no_tables", args{"unknown", 0, nil}, "unknown"}, + {"unknown_no_rate_no_attr_table", args{"unknown", 0, []table{table1, table2, table4}}, "unknown"}, + {"unknown_no_rate_value_table", args{"unknown", 0, []table{table1, table2, table4, attrTable}}, "unknown"}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := getAttributeType(tt.args.devType, tt.args.rate, tt.args.tables); got != tt.want { + t.Errorf("getAttributeType() = %v, want %v", got, tt.want) + } + }) + } +} + func Test_getType(t *testing.T) { type args struct { devType string rate int + tables []table } tests := []struct { name string args args wantOut string }{ - {"ssd", args{"SAT", 0}, "ssd"}, - {"hdd", args{"SAT", 12}, "hdd"}, - {"nvme", args{"nvme", 0}, "nvme"}, - {"unknown", args{"SAT", -1}, "unknown"}, + {"ssd_no_tables", args{"SAT", 0, nil}, "ssd"}, + {"ssd_tables_no_spin_up_table", args{"SAT", 0, []table{table1, table2, table4}}, "ssd"}, + {"hdd_no_tables", args{"SAT", 12, nil}, "hdd"}, + {"hdd_rate_spin_up_table", args{"SAT", 12, []table{table1, table2, table4, attrTable}}, "hdd"}, + {"hdd_no_rate_spin_up_table", args{"SAT", 0, []table{table1, table2, table4, attrTable}}, "hdd"}, + {"hdd_no_spin_up_table", args{"SAT", 12, []table{table1, table2, table4}}, "hdd"}, + {"nvme_no_tables", args{"nvme", 1000, nil}, "nvme"}, + {"nvme_no_attr_table", args{"nvme", 1000, []table{table1, table2, table4}}, "nvme"}, + {"nvme_value_table", args{"nvme", 1000, []table{table1, table2, table4, attrTable}}, "nvme"}, + {"nvme_no_rate_no_tables", args{"nvme", 0, nil}, "nvme"}, + {"nvme_no_rate_no_attr_table", args{"nvme", 0, []table{table1, table2, table4}}, "nvme"}, + {"nvme_no_rate_value_table", args{"nvme", 0, []table{table1, table2, table4, attrTable}}, "nvme"}, + {"unknown_no_tables", args{"unknown", 1000, nil}, "unknown"}, + {"unknown_no_attr_table", args{"unknown", 1000, []table{table1, table2, table4}}, "unknown"}, + {"unknown_value_table", args{"unknown", 1000, []table{table1, table2, table4, attrTable}}, "unknown"}, + {"unknown_no_rate_no_tables", args{"unknown", 0, nil}, "unknown"}, + {"unknown_no_rate_no_attr_table", args{"unknown", 0, []table{table1, table2, table4}}, "unknown"}, + {"unknown_no_rate_value_table", args{"unknown", 0, []table{table1, table2, table4, attrTable}}, "unknown"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if gotOut := getType(tt.args.devType, tt.args.rate); gotOut != tt.wantOut { + if gotOut := getType(tt.args.devType, tt.args.rate, tt.args.tables); gotOut != tt.wantOut { t.Errorf("getType() = %v, want %v", gotOut, tt.wantOut) } }) } } + +func Test_getTypeByRateAndAttr(t *testing.T) { + type args struct { + rate int + tables []table + } + tests := []struct { + name string + args args + want string + }{ + {"zero_rate_zero_spin_up", args{0, []table{table1, table2}}, "ssd"}, + {"zero_rate_no_tables", args{0, nil}, "ssd"}, + {"negative_rate_no_tables", args{-1000, nil}, "ssd"}, + {"positive_rate_spin_up_table", args{12, []table{table1, table2, table3, attrTable}}, "hdd"}, + {"positive_rate_no_tables", args{12, nil}, "hdd"}, + {"zero_rate_spin_up_table", args{0, []table{table1, table2, table3, attrTable}}, "hdd"}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := getTypeByRateAndAttr(tt.args.rate, tt.args.tables); got != tt.want { + t.Errorf("getTypeByRate() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/src/go/plugins/smart/smartfs.go b/src/go/plugins/smart/smartfs.go index 469579a3099..2942525c08f 100644 --- a/src/go/plugins/smart/smartfs.go +++ b/src/go/plugins/smart/smartfs.go @@ -33,9 +33,25 @@ import ( "zabbix.com/pkg/zbxerr" ) -const supportedSmartctl = 7.1 +const ( + supportedSmartctl = 7.1 -const satType = "sat" + satType = "sat" + nvmeType = "nvme" + unknownType = "unknown" + ssdType = "ssd" + hddType = "hdd" + + spinUpAttrName = "Spin_Up_Time" + + ataSmartAttrFieldName = "ata_smart_attributes" + ataSmartAttrTableFieldName = "table" + + rotationRateFieldName = "rotation_rate" + + deviceFieldName = "device" + typeFieldName = "type" +) var ( cpuCount int @@ -374,7 +390,7 @@ runner: device, err := r.plugin.executeSmartctl(fmt.Sprintf("-a %s -j ", name), false) if err != nil { r.plugin.Tracef( - "stopped looking for RAID devices of %s type, err:", + "stopped looking for RAID devices of %s type, err: %s", raid.rType, fmt.Errorf("failed to get RAID disk data from smartctl: %s", err.Error()), ) @@ -384,8 +400,8 @@ runner: var dp deviceParser if err = json.Unmarshal(device, &dp); err != nil { r.plugin.Tracef( - "stopped looking for RAID devices of %s type, err:", - raid.rType, fmt.Errorf("failed to get RAID disk data from smartctl: %s", err.Error()), + "stopped looking for RAID devices of %s type, err: %s", + raid.rType, fmt.Errorf("failed to parse RAID disk data from smartctl: %s", err.Error()), ) continue runner @@ -394,7 +410,7 @@ runner: err = dp.checkErr() if err != nil { r.plugin.Tracef( - "stopped looking for RAID devices of %s type, err:", + "stopped looking for RAID devices of %s type, err: %s", raid.rType, fmt.Errorf("failed to get disk data from smartctl: %s", err.Error()), ) diff --git a/src/go/plugins/system/cpu/cpu.go b/src/go/plugins/system/cpu/cpu.go index e2eb6e9e86c..5f22df69edf 100644 --- a/src/go/plugins/system/cpu/cpu.go +++ b/src/go/plugins/system/cpu/cpu.go @@ -23,6 +23,8 @@ import ( "encoding/json" "errors" "strconv" + + "zabbix.com/pkg/zbxerr" ) const pluginName = "Cpu" @@ -101,29 +103,21 @@ func (p *Plugin) getCpuDiscovery(params []string) (result interface{}, err error } func (p *Plugin) getCpuNum(params []string) (result interface{}, err error) { - mask := cpuStatusOnline switch len(params) { case 1: switch params[0] { case "", "online": - // default value, already initialized + return numCPUOnline(), nil case "max": - mask = cpuStatusOnline | cpuStatusOffline + return numCPUConf(), nil default: return nil, errors.New("Invalid first parameter.") } case 0: + return numCPUOnline(), nil default: - return nil, errors.New("Too many parameters.") - } - - var num int - for i := 1; i < len(p.cpus); i++ { - if p.cpus[i].status&mask != 0 { - num++ - } + return nil, zbxerr.ErrorTooManyParameters } - return num, nil } func periodByMode(mode string) (period historyIndex) { diff --git a/src/go/plugins/system/cpu/cpu_linux.go b/src/go/plugins/system/cpu/cpu_linux.go index e9eb21e8695..d52fff06fd4 100644 --- a/src/go/plugins/system/cpu/cpu_linux.go +++ b/src/go/plugins/system/cpu/cpu_linux.go @@ -77,11 +77,14 @@ func (p *Plugin) Collect() (err error) { if i, err = strconv.ParseInt(fields[0][3:], 10, 32); err != nil { return } - if index = int(i); index < 0 || index+1 >= len(p.cpus) { + + if index = int(i); index < 0 { p.Debugf("invalid CPU index %d", index) continue } + p.addCpu(index) + status = cpuStatusOnline } else { index = -1 @@ -112,12 +115,32 @@ func (p *Plugin) Collect() (err error) { return nil } -func numCPU() int { +func (p *Plugin) addCpu(index int) { + if p == nil || p.cpus == nil { + return + } + + if index == 0 { + return + } + + if index+1 >= len(p.cpus) { + for idx := p.cpus[len(p.cpus)-1].index; idx < index; idx++ { + p.cpus = append(p.cpus, &cpuUnit{index: idx + 1, status: cpuStatusOffline}) + } + } +} + +func numCPUConf() int { return int(C.sysconf(C._SC_NPROCESSORS_CONF)) } +func numCPUOnline() int { + return int(C.sysconf(C._SC_NPROCESSORS_ONLN)) +} + func (p *Plugin) Start() { - p.cpus = p.newCpus(numCPU()) + p.cpus = p.newCpus(numCPUConf()) } func (p *Plugin) Stop() { diff --git a/src/go/plugins/system/cpu/cpu_linux_test.go b/src/go/plugins/system/cpu/cpu_linux_test.go new file mode 100644 index 00000000000..87d719352ca --- /dev/null +++ b/src/go/plugins/system/cpu/cpu_linux_test.go @@ -0,0 +1,136 @@ +/* +** Zabbix +** Copyright (C) 2001-2022 Zabbix SIA +** +** This program is free software; you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation; either version 2 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program; if not, write to the Free Software +** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +**/ + +package cpu + +import ( + "reflect" + "testing" +) + +func TestPlugin_addCpu(t *testing.T) { + type fields struct { + cpus []*cpuUnit + } + type args struct { + index int + } + tests := []struct { + name string + fields fields + args args + want []*cpuUnit + }{ + { + "one_offline_cpu", + fields{ + []*cpuUnit{ + {index: -1, status: cpuStatusOffline}, + {index: 0, status: cpuStatusOffline}, + {index: 1, status: cpuStatusOffline}, + }, + }, + args{2}, + []*cpuUnit{ + {index: -1, status: cpuStatusOffline}, + {index: 0, status: cpuStatusOffline}, + {index: 1, status: cpuStatusOffline}, + {index: 2, status: cpuStatusOffline}, + }, + }, + { + "two_offline_cpu", + fields{ + []*cpuUnit{ + {index: -1, status: cpuStatusOffline}, + {index: 0, status: cpuStatusOffline}, + {index: 1, status: cpuStatusOffline}, + }, + }, + args{3}, + []*cpuUnit{ + {index: -1, status: cpuStatusOffline}, + {index: 0, status: cpuStatusOffline}, + {index: 1, status: cpuStatusOffline}, + {index: 2, status: cpuStatusOffline}, + {index: 3, status: cpuStatusOffline}, + }, + }, + { + "ten_offline_cpu", + fields{ + []*cpuUnit{ + {index: -1, status: cpuStatusOffline}, + {index: 0, status: cpuStatusOffline}, + {index: 1, status: cpuStatusOffline}, + }, + }, + args{11}, + []*cpuUnit{ + {index: -1, status: cpuStatusOffline}, + {index: 0, status: cpuStatusOffline}, + {index: 1, status: cpuStatusOffline}, + {index: 2, status: cpuStatusOffline}, + {index: 3, status: cpuStatusOffline}, + {index: 4, status: cpuStatusOffline}, + {index: 5, status: cpuStatusOffline}, + {index: 6, status: cpuStatusOffline}, + {index: 7, status: cpuStatusOffline}, + {index: 8, status: cpuStatusOffline}, + {index: 9, status: cpuStatusOffline}, + {index: 10, status: cpuStatusOffline}, + {index: 11, status: cpuStatusOffline}, + }, + }, + { + "no_offline_cpu", + fields{ + []*cpuUnit{ + {index: -1, status: cpuStatusOffline}, + {index: 0, status: cpuStatusOffline}, + {index: 1, status: cpuStatusOffline}, + }, + }, + args{1}, + []*cpuUnit{ + {index: -1, status: cpuStatusOffline}, + {index: 0, status: cpuStatusOffline}, + {index: 1, status: cpuStatusOffline}, + }, + }, + { + "empty", fields{[]*cpuUnit{}}, args{}, []*cpuUnit{}, + }, + { + "nil", fields{nil}, args{}, nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + p := &Plugin{ + cpus: tt.fields.cpus, + } + p.addCpu(tt.args.index) + + if !reflect.DeepEqual(p.cpus, tt.want) { + t.Errorf("addCpu() got = %v, want %v", p.cpus, tt.want) + } + }) + } +} diff --git a/src/go/plugins/system/cpu/cpu_windows.go b/src/go/plugins/system/cpu/cpu_windows.go index 0b175a17dfe..eff02894286 100644 --- a/src/go/plugins/system/cpu/cpu_windows.go +++ b/src/go/plugins/system/cpu/cpu_windows.go @@ -43,6 +43,15 @@ const ( defaultIndex = 60 ) +func numCPUOnline() int { + return numCPU() +} + +func numCPUConf() int { + // unsupported on Windows + return 0 +} + func numCPU() (numCpu int) { size, err := win32.GetLogicalProcessorInformationEx(win32.RelationProcessorCore, nil) if err != nil { diff --git a/src/go/plugins/systemd/systemd.go b/src/go/plugins/systemd/systemd.go index c857789de5e..c85f4b4bd18 100644 --- a/src/go/plugins/systemd/systemd.go +++ b/src/go/plugins/systemd/systemd.go @@ -24,6 +24,7 @@ import ( "fmt" "path/filepath" "reflect" + "strings" "sync" "zabbix.com/pkg/plugin" @@ -53,6 +54,11 @@ type unit struct { JobPath string } +type unitFile struct { + Name string + EnablementState string +} + type unitJson struct { Name string `json:"{#UNIT.NAME}"` Description string `json:"{#UNIT.DESCRIPTION}"` @@ -207,6 +213,11 @@ func (p *Plugin) discovery(params []string, conn *dbus.Conn) (interface{}, error return nil, fmt.Errorf("Cannot retrieve list of units: %s", err) } + var unitFiles []unitFile + if err = obj.Call("org.freedesktop.systemd1.Manager.ListUnitFiles", 0).Store(&unitFiles); err != nil { + return nil, fmt.Errorf("Cannot retrieve list of unit files: %s", err) + } + var array []unitJson for _, u := range units { if len(ext) != 0 && ext != filepath.Ext(u.Name) { @@ -233,6 +244,28 @@ func (p *Plugin) discovery(params []string, conn *dbus.Conn) (interface{}, error }) } + for _, f := range unitFiles { + unitFileExt := filepath.Ext(f.Name) + basePath := filepath.Base(f.Name) + if f.EnablementState != "disabled" || (len(ext) != 0 && ext != unitFileExt) || + strings.HasSuffix(strings.TrimSuffix(f.Name, unitFileExt), "@") || /* skip unit templates */ + isEnabledUnit(array, basePath) { + continue + } + + unitPath := "/org/freedesktop/systemd1/unit/" + getName(basePath) + + var details map[string]interface{} + obj = conn.Object("org.freedesktop.systemd1", dbus.ObjectPath(unitPath)) + err = obj.Call("org.freedesktop.DBus.Properties.GetAll", 0, "org.freedesktop.systemd1.Unit").Store(&details) + if err != nil { + p.Debugf("Cannot get unit properties for disabled unit %s, err:", basePath, err.Error()) + continue + } + + array = append(array, unitJson{basePath, "", "", "inactive", "", "", unitPath, 0, "", "", f.EnablementState}) + } + jsonArray, err := json.Marshal(array) if nil != err { return nil, fmt.Errorf("Cannot create JSON array: %s", err) @@ -335,6 +368,15 @@ func (p *Plugin) createStateMapping(v map[string]interface{}, key string, names } +func isEnabledUnit(units []unitJson, p string) bool { + for _, u := range units { + if u.Name == p { + return true + } + } + return false +} + func init() { plugin.RegisterMetrics(&impl, "Systemd", "systemd.unit.get", "Returns the bulked info, usage: systemd.unit.get[unit,<interface>].", diff --git a/src/go/plugins/zabbix/sync/sync_nix.go b/src/go/plugins/zabbix/sync/sync_nix.go index 17192f049c1..c88eae2df79 100644 --- a/src/go/plugins/zabbix/sync/sync_nix.go +++ b/src/go/plugins/zabbix/sync/sync_nix.go @@ -1,4 +1,3 @@ -//go:build !windows // +build !windows /* @@ -24,8 +23,6 @@ package zabbixsync func getMetrics() []string { return []string{ - "net.dns", "Checks if DNS service is up.", - "net.dns.record", "Performs DNS query.", "vfs.dir.get", "Directory entry list.", } } diff --git a/src/go/plugins/zabbix/sync/sync_windows.go b/src/go/plugins/zabbix/sync/sync_windows.go index 62701cfb8df..846d59d9ae1 100644 --- a/src/go/plugins/zabbix/sync/sync_windows.go +++ b/src/go/plugins/zabbix/sync/sync_windows.go @@ -21,8 +21,6 @@ package zabbixsync func getMetrics() []string { return []string{ - "net.dns", "Checks if DNS service is up.", - "net.dns.record", "Performs DNS query.", "vfs.dir.get", "Directory entry list.", } } |