Skip to content

Commit

Permalink
Interfaces: Refactor dynamic labels (#251)
Browse files Browse the repository at this point in the history
Co-authored-by: Oliver Geiselhardt-Herms <[email protected]>
  • Loading branch information
taktv6 and Oliver Geiselhardt-Herms authored Jul 26, 2024
1 parent 52dffa4 commit a6e099a
Show file tree
Hide file tree
Showing 18 changed files with 636 additions and 739 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ features:
```
## Dynamic Interface Labels
Version 0.9.5 introduced dynamic labels retrieved from the interface descriptions. Flags are supported a well. The first part (label name) has to comply to the following rules:
Version 0.9.5 introduced dynamic labels retrieved from the interface descriptions. Version 0.12.4 added support for dynamic labels on BGP metrics. Flags are supported a well. The first part (label name) has to comply to the following rules:
* must not begin with a figure
* must only contain this charakters: A-Z,a-z,0-9,_
* is treated lower case
Expand Down
17 changes: 7 additions & 10 deletions collectors.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,34 +41,31 @@ import (
"github.com/czerwonk/junos_exporter/pkg/features/system"
"github.com/czerwonk/junos_exporter/pkg/features/vpws"
"github.com/czerwonk/junos_exporter/pkg/features/vrrp"
"github.com/czerwonk/junos_exporter/pkg/interfacelabels"
)

type collectors struct {
logicalSystem string
dynamicLabels *interfacelabels.DynamicLabelManager
collectors map[string]collector.RPCCollector
devices map[string][]collector.RPCCollector
cfg *config.Config
}

func collectorsForDevices(devices []*connector.Device, cfg *config.Config, logicalSystem string, dynamicLabels *interfacelabels.DynamicLabelManager) *collectors {
func collectorsForDevices(devices []*connector.Device, cfg *config.Config, logicalSystem string) *collectors {
c := &collectors{
logicalSystem: logicalSystem,
dynamicLabels: dynamicLabels,
collectors: make(map[string]collector.RPCCollector),
devices: make(map[string][]collector.RPCCollector),
cfg: cfg,
}

for _, d := range devices {
c.initCollectorsForDevices(d, cfg.IfDescReg)
c.initCollectorsForDevices(d, deviceInterfaceRegex(cfg, d.Host))
}

return c
}

func (c *collectors) initCollectorsForDevices(device *connector.Device, bgpDescRe *regexp.Regexp) {
func (c *collectors) initCollectorsForDevices(device *connector.Device, descRe *regexp.Regexp) {
f := c.cfg.FeaturesForDevice(device.Host)

c.devices[device.Host] = make([]collector.RPCCollector, 0)
Expand All @@ -80,19 +77,19 @@ func (c *collectors) initCollectorsForDevices(device *connector.Device, bgpDescR
})
c.addCollectorIfEnabledForDevice(device, "bfd", f.BFD, bfd.NewCollector)
c.addCollectorIfEnabledForDevice(device, "bgp", f.BGP, func() collector.RPCCollector {
return bgp.NewCollector(c.logicalSystem, bgpDescRe)
return bgp.NewCollector(c.logicalSystem, descRe)
})
c.addCollectorIfEnabledForDevice(device, "env", f.Environment, environment.NewCollector)
c.addCollectorIfEnabledForDevice(device, "firewall", f.Firewall, firewall.NewCollector)
c.addCollectorIfEnabledForDevice(device, "fpc", f.FPC, fpc.NewCollector)
c.addCollectorIfEnabledForDevice(device, "ifacediag", f.InterfaceDiagnostic, func() collector.RPCCollector {
return interfacediagnostics.NewCollector(c.dynamicLabels)
return interfacediagnostics.NewCollector(descRe)
})
c.addCollectorIfEnabledForDevice(device, "ifacequeue", f.InterfaceQueue, func() collector.RPCCollector {
return interfacequeue.NewCollector(c.dynamicLabels)
return interfacequeue.NewCollector(descRe)
})
c.addCollectorIfEnabledForDevice(device, "iface", f.Interfaces, func() collector.RPCCollector {
return interfaces.NewCollector(c.dynamicLabels)
return interfaces.NewCollector(descRe)
})
c.addCollectorIfEnabledForDevice(device, "ipsec", f.IPSec, ipsec.NewCollector)
c.addCollectorIfEnabledForDevice(device, "isis", f.ISIS, isis.NewCollector)
Expand Down
5 changes: 2 additions & 3 deletions collectors_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (

"github.com/czerwonk/junos_exporter/internal/config"
"github.com/czerwonk/junos_exporter/pkg/connector"
"github.com/czerwonk/junos_exporter/pkg/interfacelabels"
)

func TestCollectorsRegistered(t *testing.T) {
Expand Down Expand Up @@ -40,7 +39,7 @@ func TestCollectorsRegistered(t *testing.T) {

cols := collectorsForDevices([]*connector.Device{{
Host: "::1",
}}, c, "", interfacelabels.NewDynamicLabelManager())
}}, c, "")

assert.Equal(t, 20, len(cols.collectors), "collector count")
}
Expand Down Expand Up @@ -88,7 +87,7 @@ func TestCollectorsForDevices(t *testing.T) {
d2 := &connector.Device{
Host: "2001:678:1e0::2",
}
cols := collectorsForDevices([]*connector.Device{d1, d2}, c, "", interfacelabels.NewDynamicLabelManager())
cols := collectorsForDevices([]*connector.Device{d1, d2}, c, "")

assert.Equal(t, 20, len(cols.collectorsForDevice(d1)), "device 1 collector count")

Expand Down
19 changes: 15 additions & 4 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ type Config struct {
IfDescReg *regexp.Regexp `yaml:"-"`
}

func (c *Config) load() error {
if c.IfDescReStr != "" {
func (c *Config) load(dynamicIfaceLabels bool) error {
if c.IfDescReStr != "" && dynamicIfaceLabels {
re, err := regexp.Compile(c.IfDescReStr)
if err != nil {
return fmt.Errorf("unable to compile interfce description regex %q: %w", c.IfDescReStr, err)
Expand All @@ -31,6 +31,17 @@ func (c *Config) load() error {
c.IfDescReg = re
}

for _, d := range c.Devices {
if d.IfDescRegStr != "" && dynamicIfaceLabels {
re, err := regexp.Compile(c.IfDescReStr)
if err != nil {
return fmt.Errorf("unable to compile interfce description regex %q: %w", c.IfDescReStr, err)
}

d.IfDescReg = re
}
}

return nil
}

Expand Down Expand Up @@ -98,7 +109,7 @@ func New() *Config {
}

// Load loads a config from reader
func Load(reader io.Reader) (*Config, error) {
func Load(reader io.Reader, dynamicIfaceLabels bool) (*Config, error) {
b, err := io.ReadAll(reader)
if err != nil {
return nil, err
Expand All @@ -110,7 +121,7 @@ func Load(reader io.Reader) (*Config, error) {
return nil, err
}

err = c.load()
err = c.load(dynamicIfaceLabels)
if err != nil {
return nil, err
}
Expand Down
12 changes: 6 additions & 6 deletions internal/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ func TestShouldParse(t *testing.T) {
t.Fatal(err)
}

c, err := Load(bytes.NewReader(b))
c, err := Load(bytes.NewReader(b), true)
if err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -49,7 +49,7 @@ func TestShouldUseDefaults(t *testing.T) {
t.Fatal(err)
}

c, err := Load(bytes.NewReader(b))
c, err := Load(bytes.NewReader(b), true)
if err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -78,7 +78,7 @@ func TestShouldParseDevices(t *testing.T) {
t.Fatal(err)
}

c, err := Load(bytes.NewReader(b))
c, err := Load(bytes.NewReader(b), true)
if err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -124,7 +124,7 @@ func TestShouldParseDevicesWithPattern(t *testing.T) {
t.Fatal(err)
}

c, err := Load(bytes.NewReader(b))
c, err := Load(bytes.NewReader(b), true)
if err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -165,7 +165,7 @@ func TestShouldParseDevicesWithPatternInvalid(t *testing.T) {
t.Fatal(err)
}

c, err := Load(bytes.NewReader(b))
c, err := Load(bytes.NewReader(b), true)
if c != nil {
t.Fatal("Parsing should fail because of invalid pattern")
}
Expand All @@ -185,7 +185,7 @@ func TestFindDeviceConfig(t *testing.T) {
if err != nil {
t.Fatal(err)
}
c, err := Load(bytes.NewReader(b))
c, err := Load(bytes.NewReader(b), true)
if err != nil {
t.Fatal(err)
}
Expand Down
38 changes: 12 additions & 26 deletions junos_collector.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,16 @@ import (
"sync"
"time"

"github.com/czerwonk/junos_exporter/internal/config"
"github.com/czerwonk/junos_exporter/pkg/connector"
"github.com/czerwonk/junos_exporter/pkg/interfacelabels"
"github.com/czerwonk/junos_exporter/pkg/dynamiclabels"
"github.com/czerwonk/junos_exporter/pkg/rpc"
"github.com/prometheus/client_golang/prometheus"
log "github.com/sirupsen/logrus"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/trace"

log "github.com/sirupsen/logrus"
)

const prefix = "junos_"
Expand All @@ -40,8 +42,6 @@ type junosCollector struct {
}

func newJunosCollector(ctx context.Context, devices []*connector.Device, logicalSystem string) *junosCollector {
l := interfacelabels.NewDynamicLabelManager()

clients := make(map[*connector.Device]*rpc.Client)

for _, d := range devices {
Expand All @@ -52,42 +52,28 @@ func newJunosCollector(ctx context.Context, devices []*connector.Device, logical
}

clients[d] = cl
cta := &clientTracingAdapter{
cl: cl,
ctx: ctx,
}

if *dynamicIfaceLabels {
regex := deviceInterfaceRegex(d.Host)
err = l.CollectDescriptions(d, cta, regex)
if err != nil {
log.Errorf("Could not get interface descriptions %s: %s", d, err)
continue
}
}
}

return &junosCollector{
devices: devices,
collectors: collectorsForDevices(devices, cfg, logicalSystem, l),
collectors: collectorsForDevices(devices, cfg, logicalSystem),
clients: clients,
ctx: ctx,
}
}

func deviceInterfaceRegex(host string) *regexp.Regexp {
func deviceInterfaceRegex(cfg *config.Config, host string) *regexp.Regexp {
dc := cfg.FindDeviceConfig(host)

if len(dc.IfDescRegStr) > 0 {
regex, err := regexp.Compile(dc.IfDescRegStr)
if err == nil {
return regex
}
if dc != nil {
return dc.IfDescReg
}

log.Errorf("device specific dynamic label regex %s invalid: %v", dc.IfDescRegStr, err)
if cfg.IfDescReg != nil {
return cfg.IfDescReg
}

return interfacelabels.DefaultInterfaceDescRegex()
return dynamiclabels.DefaultInterfaceDescRegex()
}

func clientForDevice(device *connector.Device, connManager *connector.SSHConnectionManager) (*rpc.Client, error) {
Expand Down
2 changes: 1 addition & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ func loadConfig() (*config.Config, error) {
return nil, err
}

return config.Load(bytes.NewReader(b))
return config.Load(bytes.NewReader(b), *dynamicIfaceLabels)
}

func loadConfigFromFlags() *config.Config {
Expand Down
86 changes: 86 additions & 0 deletions pkg/dynamiclabels/dynamic_labels.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// SPDX-License-Identifier: MIT

package dynamiclabels

import (
"regexp"
"strings"
)

var (
nameRe *regexp.Regexp
)

func init() {
nameRe = regexp.MustCompile(`^[a-zA-Z][a-zA-Z0-9_]*$`)
}

func DefaultInterfaceDescRegex() *regexp.Regexp {
return regexp.MustCompile(`\[([^=\]]+)(=[^\]]+)?\]`)
}

type Label struct {
name string
value string
}

func (il *Label) Name() string {
return il.name
}

func (il *Label) Value() string {
return il.value
}

func ParseDescription(description string, ifDescReg *regexp.Regexp) Labels {
labels := make(Labels, 0)

if len(description) == 0 || ifDescReg == nil {
return labels
}

matches := ifDescReg.FindAllStringSubmatch(description, -1)
for _, m := range matches {
n := strings.ToLower(m[1])

if !nameRe.Match([]byte(n)) {
continue
}

label := &Label{
name: n,
}

val := m[2]

if strings.HasPrefix(val, "=") {
label.value = val[1:]
} else {
label.value = "1"
}

labels = append(labels, label)
}

return labels
}

type Labels []*Label

func (ils Labels) Keys() []string {
ret := make([]string, 0, len(ils))
for _, il := range ils {
ret = append(ret, il.name)
}

return ret
}

func (ils Labels) Values() []string {
ret := make([]string, 0, len(ils))
for _, il := range ils {
ret = append(ret, il.value)
}

return ret
}
Loading

0 comments on commit a6e099a

Please sign in to comment.