mirror of
https://github.com/ClusterCockpit/cc-metric-collector.git
synced 2024-11-10 04:27:25 +01:00
295 lines
7.3 KiB
Go
295 lines
7.3 KiB
Go
package receivers
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"regexp"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
cclog "github.com/ClusterCockpit/cc-metric-collector/pkg/ccLogger"
|
|
lp "github.com/ClusterCockpit/cc-metric-collector/pkg/ccMetric"
|
|
"github.com/gosnmp/gosnmp"
|
|
)
|
|
|
|
type SNMPReceiverTargetConfig struct {
|
|
Hostname string `json:"hostname"`
|
|
Port int `json:"port,omitempty"`
|
|
Community string `json:"community,omitempty"`
|
|
Timeout string `json:"timeout,omitempty"`
|
|
timeout time.Duration
|
|
Version string `json:"version,omitempty"`
|
|
Type string `json:"type,omitempty"`
|
|
TypeId string `json:"type-id,omitempty"`
|
|
SubType string `json:"subtype,omitempty"`
|
|
SubTypeId string `json:"subtype-id,omitempty"`
|
|
}
|
|
|
|
type SNMPReceiverMetricConfig struct {
|
|
Name string `json:"name"`
|
|
OID string `json:"oid"`
|
|
Unit string `json:"unit,omitempty"`
|
|
}
|
|
|
|
// SNMPReceiver configuration: receiver type, listen address, port
|
|
type SNMPReceiverConfig struct {
|
|
Type string `json:"type"`
|
|
Targets []SNMPReceiverTargetConfig `json:"targets"`
|
|
Metrics []SNMPReceiverMetricConfig `json:"metrics"`
|
|
ReadInterval string `json:"read_interval,omitempty"`
|
|
}
|
|
|
|
type SNMPReceiver struct {
|
|
receiver
|
|
config SNMPReceiverConfig
|
|
|
|
// Storage for static information
|
|
meta map[string]string
|
|
tags map[string]string
|
|
// Use in case of own go routine
|
|
done chan bool
|
|
wg sync.WaitGroup
|
|
interval time.Duration
|
|
}
|
|
|
|
func validOid(oid string) bool {
|
|
// Regex from https://github.com/BornToBeRoot/NETworkManager/blob/6805740762bf19b95051c7eaa73cf2b4727733c3/Source/NETworkManager.Utilities/RegexHelper.cs#L88
|
|
// Match on leading dot added by Thomas Gruber <thomas.gruber@fau.de>
|
|
match, err := regexp.MatchString(`^[\.]?[012]\.(?:[0-9]|[1-3][0-9])(\.\d+)*$`, oid)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
return match
|
|
}
|
|
|
|
func (r *SNMPReceiver) readTarget(target SNMPReceiverTargetConfig, output chan lp.CCMetric) {
|
|
port := uint16(161)
|
|
comm := "public"
|
|
timeout := time.Duration(1) * time.Second
|
|
version := gosnmp.Version2c
|
|
timestamp := time.Now()
|
|
if target.Port > 0 {
|
|
port = uint16(target.Port)
|
|
}
|
|
if len(target.Community) > 0 {
|
|
comm = target.Community
|
|
}
|
|
if target.timeout > 0 {
|
|
timeout = target.timeout
|
|
}
|
|
if len(target.Version) > 0 {
|
|
switch target.Version {
|
|
case "1":
|
|
version = gosnmp.Version1
|
|
case "2c":
|
|
version = gosnmp.Version2c
|
|
case "3":
|
|
version = gosnmp.Version3
|
|
default:
|
|
cclog.ComponentError(r.name, "Invalid SNMP version ", target.Version)
|
|
return
|
|
}
|
|
}
|
|
params := &gosnmp.GoSNMP{
|
|
Target: target.Hostname,
|
|
Port: port,
|
|
Community: comm,
|
|
Version: version,
|
|
Timeout: timeout,
|
|
}
|
|
err := params.Connect()
|
|
if err != nil {
|
|
cclog.ComponentError(r.name, err.Error())
|
|
return
|
|
}
|
|
for _, metric := range r.config.Metrics {
|
|
if !validOid(metric.OID) {
|
|
cclog.ComponentDebug(r.name, "Skipping ", metric.Name, ", not valid OID: ", metric.OID)
|
|
continue
|
|
}
|
|
oids := make([]string, 0)
|
|
name := gosnmp.SnmpPDU{
|
|
Value: metric.Name,
|
|
Name: metric.Name,
|
|
}
|
|
nameidx := -1
|
|
value := gosnmp.SnmpPDU{
|
|
Value: nil,
|
|
Name: metric.OID,
|
|
}
|
|
valueidx := -1
|
|
unit := gosnmp.SnmpPDU{
|
|
Value: metric.Unit,
|
|
Name: metric.Unit,
|
|
}
|
|
unitidx := -1
|
|
idx := 0
|
|
if validOid(metric.Name) {
|
|
oids = append(oids, metric.Name)
|
|
nameidx = idx
|
|
idx = idx + 1
|
|
}
|
|
if validOid(metric.OID) {
|
|
oids = append(oids, metric.OID)
|
|
valueidx = idx
|
|
idx = idx + 1
|
|
}
|
|
if len(metric.Unit) > 0 && validOid(metric.Unit) {
|
|
oids = append(oids, metric.Unit)
|
|
unitidx = idx
|
|
}
|
|
//cclog.ComponentDebug(r.name, len(oids), oids)
|
|
result, err := params.Get(oids)
|
|
if err != nil {
|
|
cclog.ComponentError(r.name, "failed to get data for OIDs ", strings.Join(oids, ","), ": ", err.Error())
|
|
continue
|
|
}
|
|
if nameidx >= 0 && len(result.Variables) > nameidx {
|
|
name = result.Variables[nameidx]
|
|
}
|
|
if valueidx >= 0 && len(result.Variables) > valueidx {
|
|
value = result.Variables[valueidx]
|
|
}
|
|
if unitidx >= 0 && len(result.Variables) > unitidx {
|
|
unit = result.Variables[unitidx]
|
|
}
|
|
tags := r.tags
|
|
if len(target.Type) > 0 {
|
|
tags["type"] = target.Type
|
|
}
|
|
if len(target.TypeId) > 0 {
|
|
tags["type-id"] = target.TypeId
|
|
}
|
|
if len(target.SubType) > 0 {
|
|
tags["stype"] = target.SubType
|
|
}
|
|
if len(target.SubTypeId) > 0 {
|
|
tags["stype-id"] = target.SubTypeId
|
|
}
|
|
if value.Value != nil {
|
|
y, err := lp.New(name.Value.(string), tags, r.meta, map[string]interface{}{"value": value.Value}, timestamp)
|
|
if err == nil {
|
|
if len(unit.Name) > 0 && unit.Value != nil {
|
|
y.AddMeta("unit", unit.Value.(string))
|
|
}
|
|
output <- y
|
|
}
|
|
}
|
|
}
|
|
params.Conn.Close()
|
|
}
|
|
|
|
// Implement functions required for Receiver interface
|
|
// Start(), Close()
|
|
// See: metricReceiver.go
|
|
|
|
func (r *SNMPReceiver) Start() {
|
|
cclog.ComponentDebug(r.name, "START")
|
|
|
|
r.done = make(chan bool)
|
|
r.wg.Add(1)
|
|
go func() {
|
|
defer r.wg.Done()
|
|
|
|
// Create ticker
|
|
ticker := time.NewTicker(r.interval)
|
|
defer ticker.Stop()
|
|
|
|
for {
|
|
select {
|
|
case <-ticker.C:
|
|
// process ticker event -> continue
|
|
if r.sink != nil {
|
|
for _, t := range r.config.Targets {
|
|
select {
|
|
case <-r.done:
|
|
return
|
|
default:
|
|
r.readTarget(t, r.sink)
|
|
}
|
|
}
|
|
}
|
|
continue
|
|
case <-r.done:
|
|
return
|
|
}
|
|
}
|
|
}()
|
|
}
|
|
|
|
// Close receiver: close network connection, close files, close libraries, ...
|
|
func (r *SNMPReceiver) Close() {
|
|
cclog.ComponentDebug(r.name, "CLOSE")
|
|
|
|
r.done <- true
|
|
r.wg.Wait()
|
|
}
|
|
|
|
// New function to create a new instance of the receiver
|
|
// Initialize the receiver by giving it a name and reading in the config JSON
|
|
func NewSNMPReceiver(name string, config json.RawMessage) (Receiver, error) {
|
|
var err error = nil
|
|
r := new(SNMPReceiver)
|
|
|
|
// Set name of SNMPReceiver
|
|
// The name should be chosen in such a way that different instances of SNMPReceiver can be distinguished
|
|
r.name = fmt.Sprintf("SNMPReceiver(%s)", name)
|
|
|
|
// Set static information
|
|
r.meta = map[string]string{"source": r.name, "group": "SNMP"}
|
|
r.tags = map[string]string{"type": "node"}
|
|
|
|
// Set defaults in r.config
|
|
r.interval = time.Duration(30) * time.Second
|
|
|
|
// Read the sample receiver specific JSON config
|
|
if len(config) > 0 {
|
|
err := json.Unmarshal(config, &r.config)
|
|
if err != nil {
|
|
cclog.ComponentError(r.name, "Error reading config:", err.Error())
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
// Check that all required fields in the configuration are set
|
|
if len(r.config.Targets) == 0 {
|
|
err = fmt.Errorf("no targets configured, exiting")
|
|
cclog.ComponentError(r.name, err.Error())
|
|
return nil, err
|
|
}
|
|
|
|
if len(r.config.Metrics) == 0 {
|
|
err = fmt.Errorf("no metrics configured, exiting")
|
|
cclog.ComponentError(r.name, err.Error())
|
|
return nil, err
|
|
}
|
|
|
|
if len(r.config.ReadInterval) > 0 {
|
|
d, err := time.ParseDuration(r.config.ReadInterval)
|
|
if err != nil {
|
|
err = fmt.Errorf("failed to parse read interval, exiting")
|
|
cclog.ComponentError(r.name, err.Error())
|
|
return nil, err
|
|
}
|
|
r.interval = d
|
|
}
|
|
newtargets := make([]SNMPReceiverTargetConfig, 0)
|
|
for _, t := range r.config.Targets {
|
|
t.timeout = time.Duration(1) * time.Second
|
|
if len(t.Timeout) > 0 {
|
|
d, err := time.ParseDuration(t.Timeout)
|
|
if err != nil {
|
|
err = fmt.Errorf("failed to parse interval for target %s", t.Hostname)
|
|
cclog.ComponentError(r.name, err.Error())
|
|
continue
|
|
}
|
|
t.timeout = d
|
|
}
|
|
newtargets = append(newtargets, t)
|
|
}
|
|
r.config.Targets = newtargets
|
|
|
|
return r, nil
|
|
}
|