From a5246386723e1b69297f6a71c1cff3e287fdc38f Mon Sep 17 00:00:00 2001 From: Gaste8 Date: Tue, 30 Jul 2024 09:35:16 +0200 Subject: [PATCH] feat: add l2vpn metrics (#248) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: add l2vpn metrics * fix: l2vpn tests --------- Co-authored-by: Johannes Mùˆller Aguilar --- collectors.go | 2 + collectors_test.go | 6 +- helm/junosexporter/values.yaml | 1 + internal/config/config.go | 2 + internal/config/config_test.go | 5 +- internal/config/tests/config1.yml | 1 + internal/config/tests/config3.yml | 1 + internal/config/tests/config4.yml | 2 +- pkg/features/l2vpn/collector.go | 147 ++++++++++++++++++++++++++++++ pkg/features/l2vpn/rpc.go | 33 +++++++ 10 files changed, 196 insertions(+), 4 deletions(-) create mode 100644 pkg/features/l2vpn/collector.go create mode 100644 pkg/features/l2vpn/rpc.go diff --git a/collectors.go b/collectors.go index d1017fd6..1b94726c 100644 --- a/collectors.go +++ b/collectors.go @@ -21,6 +21,7 @@ import ( "github.com/czerwonk/junos_exporter/pkg/features/ipsec" "github.com/czerwonk/junos_exporter/pkg/features/isis" "github.com/czerwonk/junos_exporter/pkg/features/l2circuit" + "github.com/czerwonk/junos_exporter/pkg/features/l2vpn" "github.com/czerwonk/junos_exporter/pkg/features/lacp" "github.com/czerwonk/junos_exporter/pkg/features/ldp" "github.com/czerwonk/junos_exporter/pkg/features/mac" @@ -94,6 +95,7 @@ func (c *collectors) initCollectorsForDevices(device *connector.Device, descRe * c.addCollectorIfEnabledForDevice(device, "ipsec", f.IPSec, ipsec.NewCollector) c.addCollectorIfEnabledForDevice(device, "isis", f.ISIS, isis.NewCollector) c.addCollectorIfEnabledForDevice(device, "l2c", f.L2Circuit, l2circuit.NewCollector) + c.addCollectorIfEnabledForDevice(device, "l2vpn", f.L2Vpn, l2vpn.NewCollector) c.addCollectorIfEnabledForDevice(device, "lacp", f.LACP, lacp.NewCollector) c.addCollectorIfEnabledForDevice(device, "ldp", f.LDP, ldp.NewCollector) c.addCollectorIfEnabledForDevice(device, "nat", f.NAT, nat.NewCollector) diff --git a/collectors_test.go b/collectors_test.go index b180a67b..5e270fe2 100644 --- a/collectors_test.go +++ b/collectors_test.go @@ -21,6 +21,7 @@ func TestCollectorsRegistered(t *testing.T) { ISIS: true, NAT: true, L2Circuit: true, + L2Vpn: true, LDP: true, Routes: true, RoutingEngine: true, @@ -41,7 +42,7 @@ func TestCollectorsRegistered(t *testing.T) { Host: "::1", }}, c, "") - assert.Equal(t, 20, len(cols.collectors), "collector count") + assert.Equal(t, 21, len(cols.collectors), "collector count") } func TestCollectorsForDevices(t *testing.T) { @@ -54,6 +55,7 @@ func TestCollectorsForDevices(t *testing.T) { ISIS: true, NAT: true, L2Circuit: true, + L2Vpn: true, LDP: true, Routes: true, RoutingEngine: true, @@ -89,7 +91,7 @@ func TestCollectorsForDevices(t *testing.T) { } cols := collectorsForDevices([]*connector.Device{d1, d2}, c, "") - assert.Equal(t, 20, len(cols.collectorsForDevice(d1)), "device 1 collector count") + assert.Equal(t, 21, len(cols.collectorsForDevice(d1)), "device 1 collector count") cd2 := cols.collectorsForDevice(d2) assert.Equal(t, 1, len(cd2), "device 2 collector count") diff --git a/helm/junosexporter/values.yaml b/helm/junosexporter/values.yaml index 0ce99989..b224ab7b 100644 --- a/helm/junosexporter/values.yaml +++ b/helm/junosexporter/values.yaml @@ -37,6 +37,7 @@ extraArgs: [] # ospf: false # isis: false # l2circuit: false +# l2vpn: false # environment: true # routes: true # routing_engine: true diff --git a/internal/config/config.go b/internal/config/config.go index d895d5b8..4c285c90 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -70,6 +70,7 @@ type FeatureConfig struct { NAT bool `yaml:"nat,omitempty"` NAT2 bool `yaml:"nat2,omitempty"` L2Circuit bool `yaml:"l2circuit,omitempty"` + L2Vpn bool `yaml:"l2vpn,omitempty"` LACP bool `yaml:"lacp,omitempty"` LDP bool `yaml:"ldp,omitempty"` Routes bool `yaml:"routes,omitempty"` @@ -162,6 +163,7 @@ func setDefaultValues(c *Config) { f.Accounting = false f.FPC = false f.L2Circuit = false + f.L2Vpn = false f.RPKI = false f.RPM = false f.Satellite = false diff --git a/internal/config/config_test.go b/internal/config/config_test.go index 63ca75cd..85046116 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -38,6 +38,7 @@ func TestShouldParse(t *testing.T) { assertFeature("InterfaceQueue", c.Features.InterfaceQueue, true, t) assertFeature("Interfaces", c.Features.Interfaces, false, t) assertFeature("L2Circuit", c.Features.L2Circuit, true, t) + assertFeature("L2Vpn", c.Features.L2Vpn, true, t) assertFeature("Storage", c.Features.Storage, false, t) assertFeature("FPC", c.Features.FPC, true, t) assertFeature("Power", c.Features.Power, false, t) @@ -63,7 +64,7 @@ func TestShouldUseDefaults(t *testing.T) { assertFeature("Firewall", c.Features.Firewall, true, t) assertFeature("InterfaceDiagnostic", c.Features.InterfaceDiagnostic, true, t) assertFeature("Interfaces", c.Features.Interfaces, true, t) - assertFeature("L2Circuit", c.Features.L2Circuit, false, t) + assertFeature("L2VPN", c.Features.L2Vpn, false, t) assertFeature("Storage", c.Features.Storage, false, t) assertFeature("FPC", c.Features.FPC, false, t) assertFeature("InterfaceQueue", c.Features.InterfaceQueue, true, t) @@ -104,6 +105,7 @@ func TestShouldParseDevices(t *testing.T) { assertFeature("ISIS", f.ISIS, true, t) assertFeature("NAT", f.NAT, true, t) assertFeature("L2Circuit", f.L2Circuit, true, t) + assertFeature("L2Vpn", f.L2Vpn, true, t) assertFeature("LDP", f.LDP, true, t) assertFeature("Routes", f.Routes, true, t) assertFeature("RoutingEngine", f.RoutingEngine, true, t) @@ -145,6 +147,7 @@ func TestShouldParseDevicesWithPattern(t *testing.T) { assertFeature("ISIS", f.ISIS, false, t) assertFeature("NAT", f.NAT, false, t) assertFeature("L2Circuit", f.L2Circuit, false, t) + assertFeature("L2Vpn", f.L2Vpn, false, t) assertFeature("LDP", f.LDP, false, t) assertFeature("Routes", f.Routes, false, t) assertFeature("RoutingEngine", f.RoutingEngine, false, t) diff --git a/internal/config/tests/config1.yml b/internal/config/tests/config1.yml index ec40c98a..42f5af48 100644 --- a/internal/config/tests/config1.yml +++ b/internal/config/tests/config1.yml @@ -14,6 +14,7 @@ features: interface_queue: true interfaces: false l2circuit: true + l2vpn: true storage: false fpc: true firewall: false diff --git a/internal/config/tests/config3.yml b/internal/config/tests/config3.yml index 47b3dd7c..ea05e222 100644 --- a/internal/config/tests/config3.yml +++ b/internal/config/tests/config3.yml @@ -17,6 +17,7 @@ devices: interface_queue: true interfaces: true l2circuit: true + l2vpn: true storage: true fpc: true firewall: true diff --git a/internal/config/tests/config4.yml b/internal/config/tests/config4.yml index 0b2275ee..2783a32f 100644 --- a/internal/config/tests/config4.yml +++ b/internal/config/tests/config4.yml @@ -17,6 +17,7 @@ devices: interface_queue: true interfaces: true l2circuit: false + l2vpn: false storage: false fpc: false firewall: false @@ -25,4 +26,3 @@ devices: ipsec: false rpki: false power: true - diff --git a/pkg/features/l2vpn/collector.go b/pkg/features/l2vpn/collector.go new file mode 100644 index 00000000..eec7cb79 --- /dev/null +++ b/pkg/features/l2vpn/collector.go @@ -0,0 +1,147 @@ +package l2vpn + +import ( + "strconv" + "strings" + "time" + + "github.com/czerwonk/junos_exporter/pkg/collector" + "github.com/prometheus/client_golang/prometheus" +) + +var ( + l2vpnConnectionStateDesc *prometheus.Desc + l2vpnConnectionsDesc *prometheus.Desc + l2vpnMap = map[string]int{ + "EI": 0, + "EM": 1, + "VC-Dn": 2, + "CM": 3, + "CN": 4, + "OR": 5, + "OL": 6, + "LD": 7, + "RD": 8, + "LN": 9, + "RN": 10, + "XX": 11, + "MM": 12, + "BK": 13, + "PF": 14, + "RS": 15, + "LB": 16, + "VM": 17, + "NC": 18, + "WE": 19, + "NP": 20, + "->": 21, + "<-": 22, + "Up": 23, + "Dn": 24, + "CF": 25, + "SC": 26, + "LM": 27, + "RM": 28, + "IL": 29, + "MI": 30, + "ST": 31, + "PB": 32, + "SN": 33, + "RB": 34, + "HS": 35, + } +) + +func init() { + l2vpnPrefix := "junos_l2vpn_" + + lcount := []string{"target", "routing_instance"} + lstate := []string{"target", "routing_instance", "connection_id", "remote_pe", "last_change", "up_transitions", "local_interface_name"} + l2StateDescription := "A l2vpn can have one of the following state-mappings EI: 0, EM: 1, VC-Dn: 2, CM: 3, CN: 4, OR: 5, OL: 6, LD: 7, RD: 8, LN: 9, RN: 10, XX: 11, MM: 12, BK: 13, PF: 14, RS: 15, LB: 16, VM: 17, NC: 18, WE: 19, NP: 20, ->: 21, <-: 22, Up: 23, Dn: 24, CF: 25, SC: 26, LM: 27, RM: 28, IL: 29, MI: 30, ST: 31, PB: 32, SN: 33, RB: 34, HS: 35" + l2vpnConnectionsDesc = prometheus.NewDesc(l2vpnPrefix+"connection_count", "Number of l2vpn connections", lcount, nil) + l2vpnConnectionStateDesc = prometheus.NewDesc(l2vpnPrefix+"connection_status", l2StateDescription, lstate, nil) +} + +// Collector collects l2vpn metrics +type l2vpnCollector struct { +} + +// NewCollector creates a new collector +func NewCollector() collector.RPCCollector { + return &l2vpnCollector{} +} + +// Name returns the name of the collector +func (*l2vpnCollector) Name() string { + return "L2 Circuit" +} + +// Describe describes the metrics +func (*l2vpnCollector) Describe(ch chan<- *prometheus.Desc) { + ch <- l2vpnConnectionStateDesc + ch <- l2vpnConnectionsDesc +} + +// Collect collects metrics from JunOS +func (c *l2vpnCollector) Collect(client collector.Client, ch chan<- prometheus.Metric, labelValues []string) error { + return c.collectl2vpnMetrics(client, ch, labelValues) +} + +func (c *l2vpnCollector) collectl2vpnMetrics(client collector.Client, ch chan<- prometheus.Metric, labelValues []string) error { + var x = l2vpnRpc{} + err := client.RunCommandAndParse("show l2vpn connections", &x) + if err != nil { + return err + } + + instances := x.Information.RoutingInstances + + for i := 0; i < len(instances); i++ { + connCount := 0 + for s := 0; s < len(instances[i].ReferenceSite); s++ { + connCount += +len(instances[i].ReferenceSite[s].Connections) + } + l := append(labelValues, instances[i].RoutingInstanceName) + ch <- prometheus.MustNewConstMetric(l2vpnConnectionsDesc, prometheus.GaugeValue, float64(connCount), l...) + + } + + for _, a := range instances { + for _, site := range a.ReferenceSite { + // l = append(l, site.ID) + for _, conn := range site.Connections { + l := append(labelValues, a.RoutingInstanceName) + // l = append(l, site.ID) + c.collectForConnection(ch, conn, l) + } + } + } + + return nil +} + +func (c *l2vpnCollector) collectForConnection(ch chan<- prometheus.Metric, + conn l2vpnConnection, labelValues []string) { + id := conn.ID + remotePe := conn.RemotePe + lastChange := string_to_date(conn.LastChange) + upTransitions := conn.UpTransitions + localInterface := "" + if len(conn.LocalInterface) == 1 { + localInterface = conn.LocalInterface[0].Name + } + + l := append(labelValues, id, remotePe, lastChange, upTransitions, localInterface) + state := l2vpnMap[conn.StatusString] + + ch <- prometheus.MustNewConstMetric(l2vpnConnectionStateDesc, prometheus.GaugeValue, float64(state), l...) +} + +func string_to_date(date string) string { + layout := "Jan 2 15:04:05 2006" + t, err := time.Parse(layout, strings.TrimRight(date, " \n")) + if err != nil { + return "" + } + return strconv.FormatInt(t.Unix(), 10) +} diff --git a/pkg/features/l2vpn/rpc.go b/pkg/features/l2vpn/rpc.go new file mode 100644 index 00000000..621a1457 --- /dev/null +++ b/pkg/features/l2vpn/rpc.go @@ -0,0 +1,33 @@ +package l2vpn + +type l2vpnRpc struct { + Information l2vpnInformation `xml:"l2vpn-connection-information"` +} + +type l2vpnInformation struct { + RoutingInstances []l2vpnRoutingInstance `xml:"instance"` +} + +type l2vpnRoutingInstance struct { + RoutingInstanceName string `xml:"instance-name"` + ReferenceSite []l2vpnReferenceSite `xml:"reference-site"` +} + +type l2vpnReferenceSite struct { + ID string `xml:"local-site-id"` + Connections []l2vpnConnection `xml:"connection"` +} + +type l2vpnConnection struct { + ID string `xml:"connection-id"` + Type string `xml:"connection-type"` + StatusString string `xml:"connection-status"` + RemotePe string `xml:"remote-pe"` + LastChange string `xml:"last-change"` + UpTransitions string `xml:"up-transitions"` + LocalInterface []l2vpnInterface `xml:"local-interface"` +} + +type l2vpnInterface struct { + Name string `xml:"interface-name"` +}