Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement indices filters #624

Merged
merged 9 commits into from
Apr 4, 2023
108 changes: 106 additions & 2 deletions collector/collector.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"encoding/binary"
"fmt"
"net"
"regexp"
"strconv"
"strings"
"time"
Expand Down Expand Up @@ -170,7 +171,39 @@ func ScrapeTarget(ctx context.Context, target string, config *config.Module, log
}
defer snmp.Conn.Close()

getOids := config.Get
// Evaluate rules.
newGet := config.Get
newWalk := config.Walk
for _, filter := range config.Filters {
var pdus []gosnmp.SnmpPDU
sebastien-coavoux marked this conversation as resolved.
Show resolved Hide resolved
allowedList := []string{}

if snmp.Version == gosnmp.Version1 {
pdus, err = snmp.WalkAll(filter.Oid)
} else {
pdus, err = snmp.BulkWalkAll(filter.Oid)
}
// Do not try to filter anything if we had errors.
if err != nil {
level.Info(logger).Log("msg", "Error getting OID, won't do any filter on this oid", "oid", filter.Oid)
continue
sebastien-coavoux marked this conversation as resolved.
Show resolved Hide resolved
}

allowedList = filterAllowedIndices(logger, filter, pdus, allowedList)

// Update config to get only index and not walk them.
newWalk = updateWalkConfig(newWalk, filter, logger)

// Only Keep indices not involved in filters.
newCfg := updateGetConfig(newGet, filter, logger)

// We now add each index from filter to the get list.
newCfg = addAllowedIndices(filter, allowedList, logger, newCfg)

newGet = newCfg
}

getOids := newGet
maxOids := int(config.WalkParams.MaxRepetitions)
// Max Repetition can be 0, maxOids cannot. SNMPv1 can only report one OID error per call.
if maxOids == 0 || snmp.Version == gosnmp.Version1 {
Expand Down Expand Up @@ -213,7 +246,7 @@ func ScrapeTarget(ctx context.Context, target string, config *config.Module, log
getOids = getOids[oids:]
}

for _, subtree := range config.Walk {
for _, subtree := range newWalk {
var pdus []gosnmp.SnmpPDU
sebastien-coavoux marked this conversation as resolved.
Show resolved Hide resolved
level.Debug(logger).Log("msg", "Walking subtree", "oid", subtree)
walkStart := time.Now()
Expand All @@ -235,6 +268,77 @@ func ScrapeTarget(ctx context.Context, target string, config *config.Module, log
return results, nil
}

func filterAllowedIndices(logger log.Logger, filter config.DynamicFilter, pdus []gosnmp.SnmpPDU, allowedList []string) []string {
level.Debug(logger).Log("msg", "Evaluating rule for oid", "oid", filter.Oid)
for _, pdu := range pdus {
found := false
for _, val := range filter.Values {
snmpval := pduValueAsString(&pdu, "DisplayString")
level.Debug(logger).Log("config value", val, "snmp value", snmpval)

if regexp.MustCompile(val).MatchString(snmpval) {
found = true
break
}
}
if found {
pduArray := strings.Split(pdu.Name, ".")
index := pduArray[len(pduArray)-1]
level.Debug(logger).Log("msg", "Caching index", "index", index)
allowedList = append(allowedList, index)
}
}
return allowedList
}

func updateWalkConfig(walkConfig []string, filter config.DynamicFilter, logger log.Logger) []string {
newCfg := []string{}
for _, elem := range walkConfig {
found := false
for _, targetOid := range filter.Targets {
if elem == targetOid {
level.Debug(logger).Log("msg", "Deleting for walk configuration", "oid", targetOid)
found = true
break
}
}
// Oid not found in target, we walk it.
sebastien-coavoux marked this conversation as resolved.
Show resolved Hide resolved
if !found {
newCfg = append(newCfg, elem)
}
}
return newCfg
}

func updateGetConfig(getConfig []string, filter config.DynamicFilter, logger log.Logger) []string {
newCfg := []string{}
for _, elem := range getConfig {
found := false
for _, targetOid := range filter.Targets {
if strings.HasPrefix(elem, targetOid) {
found = true
break
}
}
// Oid not found in targets, we keep it.
if !found {
level.Debug(logger).Log("msg", "Keeping get configuration", "oid", elem)
newCfg = append(newCfg, elem)
}
}
return newCfg
}

func addAllowedIndices(filter config.DynamicFilter, allowedList []string, logger log.Logger, newCfg []string) []string {
for _, targetOid := range filter.Targets {
for _, index := range allowedList {
level.Debug(logger).Log("msg", "Adding get configuration", "oid", targetOid+"."+index)
newCfg = append(newCfg, targetOid+"."+index)
}
}
return newCfg
}

type MetricNode struct {
metric *config.Metric

Expand Down
145 changes: 145 additions & 0 deletions collector/collector_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1008,3 +1008,148 @@ func TestIndexesToLabels(t *testing.T) {
}
}
}

func TestFilterAllowedIndices(t *testing.T) {

pdus := []gosnmp.SnmpPDU{
gosnmp.SnmpPDU{
Name: "1.3.6.1.2.1.2.2.1.8.1",
Value: "2",
},
gosnmp.SnmpPDU{
Name: "1.3.6.1.2.1.2.2.1.8.2",
Value: "1",
},
gosnmp.SnmpPDU{
Name: "1.3.6.1.2.1.2.2.1.8.3",
Value: "1",
},
gosnmp.SnmpPDU{
Name: "1.3.6.1.2.1.2.2.1.8.4",
Value: "5",
},
}

cases := []struct {
filter config.DynamicFilter
allowedList []string
result []string
}{
{
filter: config.DynamicFilter{
Oid: "1.3.6.1.2.1.2.2.1.8",
Targets: []string{"1.3.6.1.2.1.2.2.1.3", "1.3.6.1.2.1.2.2.1.5"},
Values: []string{"1"},
},
result: []string{"2", "3"},
},
{
filter: config.DynamicFilter{
Oid: "1.3.6.1.2.1.2.2.1.8",
Targets: []string{"1.3.6.1.2.1.2.2.1.3", "1.3.6.1.2.1.2.2.1.5"},
Values: []string{"5"},
},
result: []string{"4"},
},
}
for _, c := range cases {
got := filterAllowedIndices(log.NewNopLogger(), c.filter, pdus, c.allowedList)
if !reflect.DeepEqual(got, c.result) {
t.Errorf("filterAllowedIndices(%v): got %v, want %v", c.filter, got, c.result)
}
}
}

func TestUpdateWalkConfig(t *testing.T) {
cases := []struct {
filter config.DynamicFilter
result []string
}{
{
filter: config.DynamicFilter{
Oid: "1.3.6.1.2.1.2.2.1.8",
Targets: []string{"1.3.6.1.2.1.2.2.1.3", "1.3.6.1.2.1.2.2.1.5", "1.3.6.1.2.1.2.2.1.7"},
Values: []string{"1"},
},
result: []string{},
},
{
filter: config.DynamicFilter{
Oid: "1.3.6.1.2.1.2.2.1.8",
Targets: []string{"1.3.6.1.2.1.2.2.1.3", "1.3.6.1.2.1.2.2.1.21"},
Values: []string{"1"},
},
result: []string{"1.3.6.1.2.1.2.2.1.5", "1.3.6.1.2.1.2.2.1.7"},
},
}
walkConfig := []string{"1.3.6.1.2.1.2.2.1.3", "1.3.6.1.2.1.2.2.1.5", "1.3.6.1.2.1.2.2.1.7"}
for _, c := range cases {
got := updateWalkConfig(walkConfig, c.filter, log.NewNopLogger())
if !reflect.DeepEqual(got, c.result) {
t.Errorf("updateWalkConfig(%v): got %v, want %v", c.filter, got, c.result)
}
}
}

func TestUpdateGetConfig(t *testing.T) {
cases := []struct {
filter config.DynamicFilter
result []string
}{
{
filter: config.DynamicFilter{
Oid: "1.3.6.1.2.1.2.2.1.8",
Targets: []string{"1.3.6.1.2.1.2.2.1"},
Values: []string{"1"},
},
result: []string{},
},
{
filter: config.DynamicFilter{
Oid: "1.3.6.1.2.1.2.2.1.8",
Targets: []string{"1.3.6.1.2.1.2.2.1.3", "1.3.6.1.2.1.2.2.1.21"},
Values: []string{"1"},
},
result: []string{"1.3.6.1.2.1.2.2.1.5", "1.3.6.1.2.1.2.2.1.7"},
},
}
getConfig := []string{"1.3.6.1.2.1.2.2.1.3", "1.3.6.1.2.1.2.2.1.5", "1.3.6.1.2.1.2.2.1.7"}
for _, c := range cases {
got := updateGetConfig(getConfig, c.filter, log.NewNopLogger())
if !reflect.DeepEqual(got, c.result) {
t.Errorf("updateGetConfig(%v): got %v, want %v", c.filter, got, c.result)
}
}
}

func TestAddAllowedIndices(t *testing.T) {
cases := []struct {
filter config.DynamicFilter
result []string
}{
{
filter: config.DynamicFilter{
Oid: "1.3.6.1.2.1.2.2.1.8",
Targets: []string{"1.3.6.1.2.1.2.2.1"},
Values: []string{"1"},
},
result: []string{"1.3.6.1.2.1.31.1.1.1.10", "1.3.6.1.2.1.31.1.1.1.11", "1.3.6.1.2.1.2.2.1.2", "1.3.6.1.2.1.2.2.1.3"},
},
{
filter: config.DynamicFilter{
Oid: "1.3.6.1.2.1.2.2.1.8",
Targets: []string{"1.3.6.1.2.1.2.2.1.3", "1.3.6.1.2.1.2.2.1.21"},
Values: []string{"1"},
},
result: []string{"1.3.6.1.2.1.31.1.1.1.10", "1.3.6.1.2.1.31.1.1.1.11", "1.3.6.1.2.1.2.2.1.3.2", "1.3.6.1.2.1.2.2.1.3.3", "1.3.6.1.2.1.2.2.1.21.2", "1.3.6.1.2.1.2.2.1.21.3"},
},
}
allowedList := []string{"2", "3"}
newCfg := []string{"1.3.6.1.2.1.31.1.1.1.10", "1.3.6.1.2.1.31.1.1.1.11"}
for _, c := range cases {
got := addAllowedIndices(c.filter, allowedList, log.NewNopLogger(), newCfg)
if !reflect.DeepEqual(got, c.result) {
t.Errorf("addAllowedIndices(%v): got %v, want %v", c.filter, got, c.result)
}
}
}
24 changes: 20 additions & 4 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,11 @@ type WalkParams struct {

type Module struct {
// A list of OIDs.
Walk []string `yaml:"walk,omitempty"`
Get []string `yaml:"get,omitempty"`
Metrics []*Metric `yaml:"metrics"`
WalkParams WalkParams `yaml:",inline"`
Walk []string `yaml:"walk,omitempty"`
Get []string `yaml:"get,omitempty"`
Metrics []*Metric `yaml:"metrics"`
WalkParams WalkParams `yaml:",inline"`
Filters []DynamicFilter `yaml:"filters,omitempty"`
}

func (c *Module) UnmarshalYAML(unmarshal func(interface{}) error) error {
Expand Down Expand Up @@ -191,6 +192,21 @@ func (c WalkParams) ConfigureSNMP(g *gosnmp.GoSNMP) {
g.SecurityParameters = usm
}

type Filters struct {
Static []StaticFilter `yaml:"static,omitempty"`
Dynamic []DynamicFilter `yaml:"dynamic,omitempty"`
}

type StaticFilter struct {
Targets []string `yaml:"targets,omitempty"`
Indices []string `yaml:"indices,omitempty"`
SuperQ marked this conversation as resolved.
Show resolved Hide resolved
}
type DynamicFilter struct {
Oid string `yaml:"oid"`
Targets []string `yaml:"targets,omitempty"`
Values []string `yaml:"values,omitempty"`
}

type Metric struct {
Name string `yaml:"name"`
Oid string `yaml:"oid"`
Expand Down
14 changes: 14 additions & 0 deletions generator/Dockerfile-local
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
FROM golang:latest

RUN apt-get update && \
apt-get install -y libsnmp-dev p7zip-full

COPY ./generator /bin/generator

WORKDIR "/opt"

ENTRYPOINT ["/bin/generator"]

ENV MIBDIRS mibs

CMD ["generate"]
Loading