Skip to content

Commit a6e099a

Browse files
taktv6Oliver Geiselhardt-Herms
and
Oliver Geiselhardt-Herms
authored
Interfaces: Refactor dynamic labels (#251)
Co-authored-by: Oliver Geiselhardt-Herms <ogh@deepl.com>
1 parent 52dffa4 commit a6e099a

File tree

18 files changed

+636
-739
lines changed

18 files changed

+636
-739
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ features:
214214
```
215215
216216
## Dynamic Interface Labels
217-
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:
217+
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:
218218
* must not begin with a figure
219219
* must only contain this charakters: A-Z,a-z,0-9,_
220220
* is treated lower case

collectors.go

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -41,34 +41,31 @@ import (
4141
"github.com/czerwonk/junos_exporter/pkg/features/system"
4242
"github.com/czerwonk/junos_exporter/pkg/features/vpws"
4343
"github.com/czerwonk/junos_exporter/pkg/features/vrrp"
44-
"github.com/czerwonk/junos_exporter/pkg/interfacelabels"
4544
)
4645

4746
type collectors struct {
4847
logicalSystem string
49-
dynamicLabels *interfacelabels.DynamicLabelManager
5048
collectors map[string]collector.RPCCollector
5149
devices map[string][]collector.RPCCollector
5250
cfg *config.Config
5351
}
5452

55-
func collectorsForDevices(devices []*connector.Device, cfg *config.Config, logicalSystem string, dynamicLabels *interfacelabels.DynamicLabelManager) *collectors {
53+
func collectorsForDevices(devices []*connector.Device, cfg *config.Config, logicalSystem string) *collectors {
5654
c := &collectors{
5755
logicalSystem: logicalSystem,
58-
dynamicLabels: dynamicLabels,
5956
collectors: make(map[string]collector.RPCCollector),
6057
devices: make(map[string][]collector.RPCCollector),
6158
cfg: cfg,
6259
}
6360

6461
for _, d := range devices {
65-
c.initCollectorsForDevices(d, cfg.IfDescReg)
62+
c.initCollectorsForDevices(d, deviceInterfaceRegex(cfg, d.Host))
6663
}
6764

6865
return c
6966
}
7067

71-
func (c *collectors) initCollectorsForDevices(device *connector.Device, bgpDescRe *regexp.Regexp) {
68+
func (c *collectors) initCollectorsForDevices(device *connector.Device, descRe *regexp.Regexp) {
7269
f := c.cfg.FeaturesForDevice(device.Host)
7370

7471
c.devices[device.Host] = make([]collector.RPCCollector, 0)
@@ -80,19 +77,19 @@ func (c *collectors) initCollectorsForDevices(device *connector.Device, bgpDescR
8077
})
8178
c.addCollectorIfEnabledForDevice(device, "bfd", f.BFD, bfd.NewCollector)
8279
c.addCollectorIfEnabledForDevice(device, "bgp", f.BGP, func() collector.RPCCollector {
83-
return bgp.NewCollector(c.logicalSystem, bgpDescRe)
80+
return bgp.NewCollector(c.logicalSystem, descRe)
8481
})
8582
c.addCollectorIfEnabledForDevice(device, "env", f.Environment, environment.NewCollector)
8683
c.addCollectorIfEnabledForDevice(device, "firewall", f.Firewall, firewall.NewCollector)
8784
c.addCollectorIfEnabledForDevice(device, "fpc", f.FPC, fpc.NewCollector)
8885
c.addCollectorIfEnabledForDevice(device, "ifacediag", f.InterfaceDiagnostic, func() collector.RPCCollector {
89-
return interfacediagnostics.NewCollector(c.dynamicLabels)
86+
return interfacediagnostics.NewCollector(descRe)
9087
})
9188
c.addCollectorIfEnabledForDevice(device, "ifacequeue", f.InterfaceQueue, func() collector.RPCCollector {
92-
return interfacequeue.NewCollector(c.dynamicLabels)
89+
return interfacequeue.NewCollector(descRe)
9390
})
9491
c.addCollectorIfEnabledForDevice(device, "iface", f.Interfaces, func() collector.RPCCollector {
95-
return interfaces.NewCollector(c.dynamicLabels)
92+
return interfaces.NewCollector(descRe)
9693
})
9794
c.addCollectorIfEnabledForDevice(device, "ipsec", f.IPSec, ipsec.NewCollector)
9895
c.addCollectorIfEnabledForDevice(device, "isis", f.ISIS, isis.NewCollector)

collectors_test.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import (
99

1010
"github.com/czerwonk/junos_exporter/internal/config"
1111
"github.com/czerwonk/junos_exporter/pkg/connector"
12-
"github.com/czerwonk/junos_exporter/pkg/interfacelabels"
1312
)
1413

1514
func TestCollectorsRegistered(t *testing.T) {
@@ -40,7 +39,7 @@ func TestCollectorsRegistered(t *testing.T) {
4039

4140
cols := collectorsForDevices([]*connector.Device{{
4241
Host: "::1",
43-
}}, c, "", interfacelabels.NewDynamicLabelManager())
42+
}}, c, "")
4443

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

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

internal/config/config.go

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ type Config struct {
2121
IfDescReg *regexp.Regexp `yaml:"-"`
2222
}
2323

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

34+
for _, d := range c.Devices {
35+
if d.IfDescRegStr != "" && dynamicIfaceLabels {
36+
re, err := regexp.Compile(c.IfDescReStr)
37+
if err != nil {
38+
return fmt.Errorf("unable to compile interfce description regex %q: %w", c.IfDescReStr, err)
39+
}
40+
41+
d.IfDescReg = re
42+
}
43+
}
44+
3445
return nil
3546
}
3647

@@ -98,7 +109,7 @@ func New() *Config {
98109
}
99110

100111
// Load loads a config from reader
101-
func Load(reader io.Reader) (*Config, error) {
112+
func Load(reader io.Reader, dynamicIfaceLabels bool) (*Config, error) {
102113
b, err := io.ReadAll(reader)
103114
if err != nil {
104115
return nil, err
@@ -110,7 +121,7 @@ func Load(reader io.Reader) (*Config, error) {
110121
return nil, err
111122
}
112123

113-
err = c.load()
124+
err = c.load(dynamicIfaceLabels)
114125
if err != nil {
115126
return nil, err
116127
}

internal/config/config_test.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ func TestShouldParse(t *testing.T) {
1717
t.Fatal(err)
1818
}
1919

20-
c, err := Load(bytes.NewReader(b))
20+
c, err := Load(bytes.NewReader(b), true)
2121
if err != nil {
2222
t.Fatal(err)
2323
}
@@ -49,7 +49,7 @@ func TestShouldUseDefaults(t *testing.T) {
4949
t.Fatal(err)
5050
}
5151

52-
c, err := Load(bytes.NewReader(b))
52+
c, err := Load(bytes.NewReader(b), true)
5353
if err != nil {
5454
t.Fatal(err)
5555
}
@@ -78,7 +78,7 @@ func TestShouldParseDevices(t *testing.T) {
7878
t.Fatal(err)
7979
}
8080

81-
c, err := Load(bytes.NewReader(b))
81+
c, err := Load(bytes.NewReader(b), true)
8282
if err != nil {
8383
t.Fatal(err)
8484
}
@@ -124,7 +124,7 @@ func TestShouldParseDevicesWithPattern(t *testing.T) {
124124
t.Fatal(err)
125125
}
126126

127-
c, err := Load(bytes.NewReader(b))
127+
c, err := Load(bytes.NewReader(b), true)
128128
if err != nil {
129129
t.Fatal(err)
130130
}
@@ -165,7 +165,7 @@ func TestShouldParseDevicesWithPatternInvalid(t *testing.T) {
165165
t.Fatal(err)
166166
}
167167

168-
c, err := Load(bytes.NewReader(b))
168+
c, err := Load(bytes.NewReader(b), true)
169169
if c != nil {
170170
t.Fatal("Parsing should fail because of invalid pattern")
171171
}
@@ -185,7 +185,7 @@ func TestFindDeviceConfig(t *testing.T) {
185185
if err != nil {
186186
t.Fatal(err)
187187
}
188-
c, err := Load(bytes.NewReader(b))
188+
c, err := Load(bytes.NewReader(b), true)
189189
if err != nil {
190190
t.Fatal(err)
191191
}

junos_collector.go

Lines changed: 12 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,16 @@ import (
88
"sync"
99
"time"
1010

11+
"github.com/czerwonk/junos_exporter/internal/config"
1112
"github.com/czerwonk/junos_exporter/pkg/connector"
12-
"github.com/czerwonk/junos_exporter/pkg/interfacelabels"
13+
"github.com/czerwonk/junos_exporter/pkg/dynamiclabels"
1314
"github.com/czerwonk/junos_exporter/pkg/rpc"
1415
"github.com/prometheus/client_golang/prometheus"
15-
log "github.com/sirupsen/logrus"
1616
"go.opentelemetry.io/otel/attribute"
1717
"go.opentelemetry.io/otel/codes"
1818
"go.opentelemetry.io/otel/trace"
19+
20+
log "github.com/sirupsen/logrus"
1921
)
2022

2123
const prefix = "junos_"
@@ -40,8 +42,6 @@ type junosCollector struct {
4042
}
4143

4244
func newJunosCollector(ctx context.Context, devices []*connector.Device, logicalSystem string) *junosCollector {
43-
l := interfacelabels.NewDynamicLabelManager()
44-
4545
clients := make(map[*connector.Device]*rpc.Client)
4646

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

5454
clients[d] = cl
55-
cta := &clientTracingAdapter{
56-
cl: cl,
57-
ctx: ctx,
58-
}
59-
60-
if *dynamicIfaceLabels {
61-
regex := deviceInterfaceRegex(d.Host)
62-
err = l.CollectDescriptions(d, cta, regex)
63-
if err != nil {
64-
log.Errorf("Could not get interface descriptions %s: %s", d, err)
65-
continue
66-
}
67-
}
6855
}
6956

7057
return &junosCollector{
7158
devices: devices,
72-
collectors: collectorsForDevices(devices, cfg, logicalSystem, l),
59+
collectors: collectorsForDevices(devices, cfg, logicalSystem),
7360
clients: clients,
7461
ctx: ctx,
7562
}
7663
}
7764

78-
func deviceInterfaceRegex(host string) *regexp.Regexp {
65+
func deviceInterfaceRegex(cfg *config.Config, host string) *regexp.Regexp {
7966
dc := cfg.FindDeviceConfig(host)
8067

81-
if len(dc.IfDescRegStr) > 0 {
82-
regex, err := regexp.Compile(dc.IfDescRegStr)
83-
if err == nil {
84-
return regex
85-
}
68+
if dc != nil {
69+
return dc.IfDescReg
70+
}
8671

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

90-
return interfacelabels.DefaultInterfaceDescRegex()
76+
return dynamiclabels.DefaultInterfaceDescRegex()
9177
}
9278

9379
func clientForDevice(device *connector.Device, connManager *connector.SSHConnectionManager) (*rpc.Client, error) {

main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,7 @@ func loadConfig() (*config.Config, error) {
205205
return nil, err
206206
}
207207

208-
return config.Load(bytes.NewReader(b))
208+
return config.Load(bytes.NewReader(b), *dynamicIfaceLabels)
209209
}
210210

211211
func loadConfigFromFlags() *config.Config {

pkg/dynamiclabels/dynamic_labels.go

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
// SPDX-License-Identifier: MIT
2+
3+
package dynamiclabels
4+
5+
import (
6+
"regexp"
7+
"strings"
8+
)
9+
10+
var (
11+
nameRe *regexp.Regexp
12+
)
13+
14+
func init() {
15+
nameRe = regexp.MustCompile(`^[a-zA-Z][a-zA-Z0-9_]*$`)
16+
}
17+
18+
func DefaultInterfaceDescRegex() *regexp.Regexp {
19+
return regexp.MustCompile(`\[([^=\]]+)(=[^\]]+)?\]`)
20+
}
21+
22+
type Label struct {
23+
name string
24+
value string
25+
}
26+
27+
func (il *Label) Name() string {
28+
return il.name
29+
}
30+
31+
func (il *Label) Value() string {
32+
return il.value
33+
}
34+
35+
func ParseDescription(description string, ifDescReg *regexp.Regexp) Labels {
36+
labels := make(Labels, 0)
37+
38+
if len(description) == 0 || ifDescReg == nil {
39+
return labels
40+
}
41+
42+
matches := ifDescReg.FindAllStringSubmatch(description, -1)
43+
for _, m := range matches {
44+
n := strings.ToLower(m[1])
45+
46+
if !nameRe.Match([]byte(n)) {
47+
continue
48+
}
49+
50+
label := &Label{
51+
name: n,
52+
}
53+
54+
val := m[2]
55+
56+
if strings.HasPrefix(val, "=") {
57+
label.value = val[1:]
58+
} else {
59+
label.value = "1"
60+
}
61+
62+
labels = append(labels, label)
63+
}
64+
65+
return labels
66+
}
67+
68+
type Labels []*Label
69+
70+
func (ils Labels) Keys() []string {
71+
ret := make([]string, 0, len(ils))
72+
for _, il := range ils {
73+
ret = append(ret, il.name)
74+
}
75+
76+
return ret
77+
}
78+
79+
func (ils Labels) Values() []string {
80+
ret := make([]string, 0, len(ils))
81+
for _, il := range ils {
82+
ret = append(ret, il.value)
83+
}
84+
85+
return ret
86+
}

0 commit comments

Comments
 (0)