Skip to content

Commit 33d47c8

Browse files
authored
Merge pull request #159 from tynany/rpki-cache-connection
RPKI cache-connection status metrics
2 parents 7d64eac + 77281ad commit 33d47c8

7 files changed

Lines changed: 191 additions & 20 deletions

File tree

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ Flags:
6565
--[no-]collector.pim Enable the pim collector (default: disabled).
6666
--[no-]collector.route Enable the route collector (default: enabled, to disable use
6767
--no-collector.route).
68+
--[no-]collector.rpki Enable the rpki collector (default: disabled).
6869
--[no-]collector.vrrp Enable the vrrp collector (default: disabled).
6970
--web.telemetry-path="/metrics"
7071
Path under which to expose metrics.
@@ -136,6 +137,7 @@ Name | Description
136137
--- | ---
137138
BGP IPv6 | Per VRF and address family (currently support unicast only) BGP IPv6 metrics:<br> - RIB entries<br> - RIB memory usage<br> - Configured peer count<br> - Peer memory usage<br> - Configure peer group count<br> - Peer group memory usage<br> - Peer messages in<br> - Peer messages out<br> - Peer active prfixes<br> - Peer state (established/down)<br> - Peer uptime
138139
BGP L2VPN | Per VRF and address family (currently support EVPN only) BGP L2VPN EVPN metrics:<br> - RIB entries<br> - RIB memory usage<br> - Configured peer count<br> - Peer memory usage<br> - Configure peer group count<br> - Peer group memory usage<br> - Peer messages in<br> - Peer messages out<br> - Peer active prfixes<br> - Peer state (established/down)<br> - Peer uptime
140+
RPKI | Per VRF RPKI cache-connection metrics (requires FRR compiled with `--enable-rpki`):<br> - Cache connection state (connected/disconnected)<br> - Cache connection preference
139141
VRRP | Per VRRP Interface, VrID and Protocol:<br> - Rx and TX statistics<br> - VRRP Status<br> - VRRP State Transitions<br>
140142
PIM | PIM metrics:<br> - Neighbor count<br> - Neighbor uptime
141143

collector/collector.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"fmt"
55
"log/slog"
66
"strconv"
7+
"strings"
78
"sync"
89
"time"
910

@@ -155,3 +156,22 @@ func newCounter(ch chan<- prometheus.Metric, descName *prometheus.Desc, metric f
155156
func cmdOutputProcessError(cmd, output string, err error) error {
156157
return fmt.Errorf("cannot process output of %s: %w: command output: %s", cmd, err, output)
157158
}
159+
160+
func getVRFs() ([]string, error) {
161+
output, err := executeZebraCommand("show vrf")
162+
if err != nil {
163+
return nil, err
164+
}
165+
return parseVRFs(output), nil
166+
}
167+
168+
func parseVRFs(output []byte) []string {
169+
vrfs := []string{"default"}
170+
for _, line := range strings.Split(string(output), "\n") {
171+
fields := strings.Fields(line)
172+
if len(fields) >= 2 && fields[0] == "vrf" {
173+
vrfs = append(vrfs, fields[1])
174+
}
175+
}
176+
return vrfs
177+
}

collector/route.go

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import (
44
"encoding/json"
55
"fmt"
66
"log/slog"
7-
"strings"
87

98
"github.com/alecthomas/kingpin/v2"
109
"github.com/prometheus/client_golang/prometheus"
@@ -136,25 +135,6 @@ func emitRouteSummaryMetrics(ch chan<- prometheus.Metric, rs routeSummary, afi s
136135
}
137136
}
138137

139-
func getVRFs() ([]string, error) {
140-
output, err := executeZebraCommand("show vrf")
141-
if err != nil {
142-
return nil, err
143-
}
144-
return parseVRFs(output), nil
145-
}
146-
147-
func parseVRFs(output []byte) []string {
148-
vrfs := []string{"default"}
149-
for _, line := range strings.Split(string(output), "\n") {
150-
fields := strings.Fields(line)
151-
if len(fields) >= 2 && fields[0] == "vrf" {
152-
vrfs = append(vrfs, fields[1])
153-
}
154-
}
155-
return vrfs
156-
}
157-
158138
type routeSummary struct {
159139
Routes []route `json:"routes"`
160140
RoutesTotal uint32 `json:"routesTotal"`

collector/rpki.go

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
package collector
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"log/slog"
7+
"strconv"
8+
9+
"github.com/prometheus/client_golang/prometheus"
10+
)
11+
12+
var rpkiSubsystem = "rpki"
13+
14+
func init() {
15+
registerCollector(rpkiSubsystem, disabledByDefault, NewRPKICollector)
16+
}
17+
18+
type rpkiCollector struct {
19+
logger *slog.Logger
20+
descriptions map[string]*prometheus.Desc
21+
}
22+
23+
// NewRPKICollector collects RPKI cache-connection metrics, implemented as per the Collector interface.
24+
func NewRPKICollector(logger *slog.Logger) (Collector, error) {
25+
return &rpkiCollector{logger: logger, descriptions: getRPKIDesc()}, nil
26+
}
27+
28+
func getRPKIDesc() map[string]*prometheus.Desc {
29+
labels := []string{"vrf", "mode", "host", "port"}
30+
return map[string]*prometheus.Desc{
31+
"cacheState": colPromDesc(rpkiSubsystem, "cache_state", "State of the RPKI cache connection (1 = connected, 0 = disconnected).", labels),
32+
"cachePreference": colPromDesc(rpkiSubsystem, "cache_preference", "Preference value of the RPKI cache connection.", labels),
33+
}
34+
}
35+
36+
// Update implemented as per the Collector interface.
37+
func (c *rpkiCollector) Update(ch chan<- prometheus.Metric) error {
38+
vrfs, err := getVRFs()
39+
if err != nil {
40+
return err
41+
}
42+
43+
for _, vrf := range vrfs {
44+
var cmd string
45+
if vrf == "default" {
46+
cmd = "show rpki cache-connection json"
47+
} else {
48+
cmd = fmt.Sprintf("show rpki cache-connection vrf %s json", vrf)
49+
}
50+
51+
output, err := executeBGPCommand(cmd)
52+
if err != nil {
53+
return err
54+
}
55+
if len(output) == 0 {
56+
continue
57+
}
58+
59+
if err := processRPKICacheConnection(ch, output, vrf, c.descriptions); err != nil {
60+
return cmdOutputProcessError(cmd, string(output), err)
61+
}
62+
}
63+
return nil
64+
}
65+
66+
func processRPKICacheConnection(ch chan<- prometheus.Metric, jsonRPKI []byte, vrf string, rpkiDesc map[string]*prometheus.Desc) error {
67+
var cacheConn rpkiCacheConnection
68+
if err := json.Unmarshal(jsonRPKI, &cacheConn); err != nil {
69+
return err
70+
}
71+
72+
for _, conn := range cacheConn.Connections {
73+
labels := []string{vrf, conn.Mode, conn.Host, strconv.Itoa(conn.Port)}
74+
75+
state := 0.0
76+
if conn.State == "connected" {
77+
state = 1.0
78+
}
79+
80+
newGauge(ch, rpkiDesc["cacheState"], state, labels...)
81+
newGauge(ch, rpkiDesc["cachePreference"], float64(conn.Preference), labels...)
82+
}
83+
return nil
84+
}
85+
86+
type rpkiCacheConnection struct {
87+
ConnectedGroup int `json:"connectedGroup"`
88+
Connections []rpkiConnection `json:"connections"`
89+
}
90+
91+
type rpkiConnection struct {
92+
Mode string `json:"mode"`
93+
Host string `json:"host"`
94+
Port int `json:"port,string"`
95+
Preference int `json:"preference"`
96+
State string `json:"state"`
97+
}

collector/rpki_test.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package collector
2+
3+
import (
4+
"testing"
5+
6+
"github.com/prometheus/client_golang/prometheus"
7+
)
8+
9+
var expectedRPKIMetrics = map[string]float64{
10+
"frr_rpki_cache_state{host=172.20.15.59,mode=tcp,port=8082,vrf=default}": 1,
11+
"frr_rpki_cache_preference{host=172.20.15.59,mode=tcp,port=8082,vrf=default}": 10,
12+
"frr_rpki_cache_state{host=172.20.15.60,mode=tcp,port=8083,vrf=default}": 0,
13+
"frr_rpki_cache_preference{host=172.20.15.60,mode=tcp,port=8083,vrf=default}": 20,
14+
}
15+
16+
func TestProcessRPKICacheConnection(t *testing.T) {
17+
ch := make(chan prometheus.Metric, 1024)
18+
if err := processRPKICacheConnection(ch, readTestFixture(t, "show_rpki_cache_connection.json"), "default", getRPKIDesc()); err != nil {
19+
t.Errorf("error calling processRPKICacheConnection: %s", err)
20+
}
21+
close(ch)
22+
23+
gotMetrics := collectMetrics(t, ch)
24+
compareMetrics(t, gotMetrics, expectedRPKIMetrics)
25+
}
26+
27+
var expectedRPKIVRFMetrics = map[string]float64{
28+
"frr_rpki_cache_state{host=172.20.15.59,mode=tcp,port=8082,vrf=TEST}": 1,
29+
"frr_rpki_cache_preference{host=172.20.15.59,mode=tcp,port=8082,vrf=TEST}": 10,
30+
}
31+
32+
func TestProcessRPKICacheConnectionVRF(t *testing.T) {
33+
ch := make(chan prometheus.Metric, 1024)
34+
if err := processRPKICacheConnection(ch, readTestFixture(t, "show_rpki_cache_connection_vrf_TEST.json"), "TEST", getRPKIDesc()); err != nil {
35+
t.Errorf("error calling processRPKICacheConnection VRF: %s", err)
36+
}
37+
close(ch)
38+
39+
gotMetrics := collectMetrics(t, ch)
40+
compareMetrics(t, gotMetrics, expectedRPKIVRFMetrics)
41+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"connectedGroup":10,
3+
"connections":[
4+
{
5+
"mode":"tcp",
6+
"host":"172.20.15.59",
7+
"port":"8082",
8+
"preference":10,
9+
"state":"connected"
10+
},
11+
{
12+
"mode":"tcp",
13+
"host":"172.20.15.60",
14+
"port":"8083",
15+
"preference":20,
16+
"state":"disconnected"
17+
}
18+
]
19+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"connectedGroup":10,
3+
"connections":[
4+
{
5+
"mode":"tcp",
6+
"host":"172.20.15.59",
7+
"port":"8082",
8+
"preference":10,
9+
"state":"connected"
10+
}
11+
]
12+
}

0 commit comments

Comments
 (0)