Skip to content

Commit

Permalink
Implement indices filters (#624)
Browse files Browse the repository at this point in the history
* Generator: Add a Dockerfile for local docker image
* Generator : Add static filters for OID index
* Collector - Add dynamic filter before scraping. Keep collector stateless
---------

Signed-off-by: Sébastien Coavoux <[email protected]>
  • Loading branch information
sebastien-coavoux authored Apr 4, 2023
1 parent 87bf22c commit 17510da
Show file tree
Hide file tree
Showing 7 changed files with 396 additions and 25 deletions.
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
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
}

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
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.
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"`
}
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

0 comments on commit 17510da

Please sign in to comment.