From 4dcf85c915d880bec832b7891fa45cab6dbb9c41 Mon Sep 17 00:00:00 2001 From: Daniel Czerwonk <czerwonk@users.noreply.github.com> Date: Thu, 28 Mar 2019 13:28:31 +0100 Subject: [PATCH] BGP and VRF metrics (#204) * first steps to metrics * next small step * fixed tests * added comments * postponed RouterAccepted and RoutesRejected to next step * read update counters * added test * added missing test file * Route not used any more * added metrics for session state and time since establish * increased test coverage, time since establish impl. * added prom collector * added metrics endpoint to BGP example * added metrics for VRF * removed commented metrics * fixed logging inconsistency in example * Update main.go * gofmt * fixed typo --- examples/bgp/main.go | 18 ++ metrics/bgp/adapter/prom/bgp_prom_adapter.go | 111 +++++++ metrics/vrf/adapter/prom/vrf_prom_adapter.go | 52 +++ .../bgp/metrics/bgp_address_family_metrics.go | 16 + protocols/bgp/metrics/bgp_metrics.go | 7 + protocols/bgp/metrics/bgp_peer_metrics.go | 40 +++ protocols/bgp/server/fsm.go | 31 +- protocols/bgp/server/fsm_counters.go | 11 + protocols/bgp/server/fsm_established.go | 5 + protocols/bgp/server/metrics_service.go | 100 ++++++ protocols/bgp/server/metrics_service_test.go | 299 ++++++++++++++++++ protocols/bgp/server/peer.go | 3 + protocols/bgp/server/server.go | 22 +- protocols/bgp/server/update_sender.go | 2 + routingtable/locRIB/loc_rib.go | 7 +- routingtable/mock_client.go | 5 +- routingtable/vrf/metrics.go | 37 +++ routingtable/vrf/metrics/rib_metrics.go | 16 + routingtable/vrf/metrics/vrf_metrics.go | 10 + routingtable/vrf/metrics_test.go | 83 +++++ routingtable/vrf/vrf.go | 11 + routingtable/vrf/vrf_registry.go | 14 + 22 files changed, 884 insertions(+), 16 deletions(-) create mode 100644 metrics/bgp/adapter/prom/bgp_prom_adapter.go create mode 100644 metrics/vrf/adapter/prom/vrf_prom_adapter.go create mode 100644 protocols/bgp/metrics/bgp_address_family_metrics.go create mode 100644 protocols/bgp/metrics/bgp_metrics.go create mode 100644 protocols/bgp/metrics/bgp_peer_metrics.go create mode 100644 protocols/bgp/server/fsm_counters.go create mode 100644 protocols/bgp/server/metrics_service.go create mode 100644 protocols/bgp/server/metrics_service_test.go create mode 100644 routingtable/vrf/metrics.go create mode 100644 routingtable/vrf/metrics/rib_metrics.go create mode 100644 routingtable/vrf/metrics/vrf_metrics.go create mode 100644 routingtable/vrf/metrics_test.go diff --git a/examples/bgp/main.go b/examples/bgp/main.go index e2e7d9d8..54047b24 100644 --- a/examples/bgp/main.go +++ b/examples/bgp/main.go @@ -2,7 +2,13 @@ package main import ( "log" + "net/http" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promhttp" + + prom_bgp "github.com/bio-routing/bio-rd/metrics/bgp/adapter/prom" + prom_vrf "github.com/bio-routing/bio-rd/metrics/vrf/adapter/prom" bnet "github.com/bio-routing/bio-rd/net" "github.com/bio-routing/bio-rd/protocols/bgp/server" "github.com/bio-routing/bio-rd/routingtable/vrf" @@ -23,7 +29,19 @@ func main() { log.Fatal(err) } + go startMetricsEndpoint(b) + startServer(b, v) select {} } + +func startMetricsEndpoint(server server.BGPServer) { + prometheus.MustRegister(prom_bgp.NewCollector(server)) + prometheus.MustRegister(prom_vrf.NewCollector()) + + http.Handle("/metrics", promhttp.Handler()) + + logrus.Info("Metrics are available :8080/metrics") + logrus.Error(http.ListenAndServe(":8080", nil)) +} diff --git a/metrics/bgp/adapter/prom/bgp_prom_adapter.go b/metrics/bgp/adapter/prom/bgp_prom_adapter.go new file mode 100644 index 00000000..2ba446e7 --- /dev/null +++ b/metrics/bgp/adapter/prom/bgp_prom_adapter.go @@ -0,0 +1,111 @@ +package prom + +import ( + "strconv" + "time" + + "github.com/bio-routing/bio-rd/protocols/bgp/metrics" + "github.com/bio-routing/bio-rd/protocols/bgp/server" + "github.com/pkg/errors" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/common/log" +) + +const ( + prefix = "bio_bgp_" +) + +var ( + upDesc *prometheus.Desc + stateDesc *prometheus.Desc + uptimeDesc *prometheus.Desc + updatesReceivedDesc *prometheus.Desc + updatesSentDesc *prometheus.Desc + routesReceivedDesc *prometheus.Desc + routesSentDesc *prometheus.Desc + routesRejectedDesc *prometheus.Desc + routesAcceptedDesc *prometheus.Desc +) + +func init() { + labels := []string{"peer_ip", "local_asn", "peer_asn", "vrf"} + upDesc = prometheus.NewDesc(prefix+"up", "Returns if the session is up", labels, nil) + stateDesc = prometheus.NewDesc(prefix+"state", "State of the BGP session (Down = 0, Idle = 1, Connect = 2, Active = 3, OpenSent = 4, OpenConfirm = 5, Established = 6)", labels, nil) + uptimeDesc = prometheus.NewDesc(prefix+"uptime_second", "Time since the session was established in seconds", labels, nil) + updatesReceivedDesc = prometheus.NewDesc(prefix+"update_received_count", "Number of updates received", labels, nil) + updatesSentDesc = prometheus.NewDesc(prefix+"update_sent_count", "Number of updates sent", labels, nil) + + labels = append(labels, "afi", "safi") + routesReceivedDesc = prometheus.NewDesc(prefix+"route_received_count", "Number of routes received", labels, nil) + routesSentDesc = prometheus.NewDesc(prefix+"route_sent_count", "Number of routes sent", labels, nil) + routesRejectedDesc = prometheus.NewDesc(prefix+"route_rejected_count", "Number of routes rejected", labels, nil) + routesAcceptedDesc = prometheus.NewDesc(prefix+"route_accepted_count", "Number of routes accepted", labels, nil) +} + +// NewCollector creates a new collector instance for the given BGP server +func NewCollector(server server.BGPServer) prometheus.Collector { + return &bgpCollector{server} +} + +// BGPCollector provides a collector for BGP metrics of BIO to use with Prometheus +type bgpCollector struct { + server server.BGPServer +} + +// Describe conforms to the prometheus collector interface +func (c *bgpCollector) Describe(ch chan<- *prometheus.Desc) { + ch <- upDesc + ch <- stateDesc + ch <- uptimeDesc + ch <- updatesReceivedDesc + ch <- updatesSentDesc + ch <- routesReceivedDesc + ch <- routesSentDesc + ch <- routesRejectedDesc + ch <- routesAcceptedDesc +} + +// Collect conforms to the prometheus collector interface +func (c *bgpCollector) Collect(ch chan<- prometheus.Metric) { + m, err := c.server.Metrics() + if err != nil { + log.Error(errors.Wrap(err, "Could not retrieve metrics from BGP server")) + return + } + + for _, peer := range m.Peers { + c.collectForPeer(ch, peer) + } +} + +func (c *bgpCollector) collectForPeer(ch chan<- prometheus.Metric, peer *metrics.BGPPeerMetrics) { + l := []string{ + peer.IP.String(), + strconv.Itoa(int(peer.LocalASN)), + strconv.Itoa(int(peer.ASN)), + peer.VRF} + + var up float64 + var uptime float64 + if peer.Up { + up = 1 + uptime = float64(time.Since(peer.Since) * time.Second) + } + ch <- prometheus.MustNewConstMetric(upDesc, prometheus.GaugeValue, up, l...) + ch <- prometheus.MustNewConstMetric(uptimeDesc, prometheus.GaugeValue, uptime, l...) + ch <- prometheus.MustNewConstMetric(stateDesc, prometheus.GaugeValue, float64(peer.State), l...) + + ch <- prometheus.MustNewConstMetric(updatesReceivedDesc, prometheus.CounterValue, float64(peer.UpdatesReceived), l...) + ch <- prometheus.MustNewConstMetric(updatesSentDesc, prometheus.CounterValue, float64(peer.UpdatesSent), l...) + + for _, family := range peer.AddressFamilies { + c.collectForFamily(ch, family, l) + } +} + +func (c *bgpCollector) collectForFamily(ch chan<- prometheus.Metric, family *metrics.BGPAddressFamilyMetrics, l []string) { + l = append(l, strconv.Itoa(int(family.AFI)), strconv.Itoa(int(family.SAFI))) + + ch <- prometheus.MustNewConstMetric(routesReceivedDesc, prometheus.CounterValue, float64(family.RoutesReceived), l...) + ch <- prometheus.MustNewConstMetric(routesSentDesc, prometheus.CounterValue, float64(family.RoutesSent), l...) +} diff --git a/metrics/vrf/adapter/prom/vrf_prom_adapter.go b/metrics/vrf/adapter/prom/vrf_prom_adapter.go new file mode 100644 index 00000000..f2a4fa0c --- /dev/null +++ b/metrics/vrf/adapter/prom/vrf_prom_adapter.go @@ -0,0 +1,52 @@ +package prom + +import ( + "strconv" + + "github.com/bio-routing/bio-rd/protocols/bgp/server" + "github.com/bio-routing/bio-rd/routingtable/vrf" + "github.com/bio-routing/bio-rd/routingtable/vrf/metrics" + "github.com/prometheus/client_golang/prometheus" +) + +const ( + prefix = "bio_vrf_" +) + +var ( + routeCountDesc *prometheus.Desc +) + +func init() { + labels := []string{"vrf", "rib", "afi", "safi"} + routeCountDesc = prometheus.NewDesc(prefix+"route_count", "Number of routes in the RIB", labels, nil) +} + +// NewCollector creates a new collector instance for the given BGP server +func NewCollector() prometheus.Collector { + return &vrfCollector{} +} + +// BGPCollector provides a collector for BGP metrics of BIO to use with Prometheus +type vrfCollector struct { + server server.BGPServer +} + +// Describe conforms to the prometheus collector interface +func (c *vrfCollector) Describe(ch chan<- *prometheus.Desc) { + ch <- routeCountDesc +} + +// Collect conforms to the prometheus collector interface +func (c *vrfCollector) Collect(ch chan<- prometheus.Metric) { + for _, v := range vrf.Metrics() { + c.collectForVRF(ch, v) + } +} + +func (c *vrfCollector) collectForVRF(ch chan<- prometheus.Metric, v *metrics.VRFMetrics) { + for _, rib := range v.RIBs { + ch <- prometheus.MustNewConstMetric(routeCountDesc, prometheus.GaugeValue, float64(rib.RouteCount), + v.Name, rib.Name, strconv.Itoa(int(rib.AFI)), strconv.Itoa(int(rib.SAFI))) + } +} diff --git a/protocols/bgp/metrics/bgp_address_family_metrics.go b/protocols/bgp/metrics/bgp_address_family_metrics.go new file mode 100644 index 00000000..5458358b --- /dev/null +++ b/protocols/bgp/metrics/bgp_address_family_metrics.go @@ -0,0 +1,16 @@ +package metrics + +// BGPAddressFamilyMetrics provides metrics on AFI/SAFI level for one session +type BGPAddressFamilyMetrics struct { + // AFI is the identifier for the address family + AFI uint16 + + // SAFI is the identifier for the sub address family + SAFI uint8 + + // RoutesReceived is the number of routes we recevied + RoutesReceived uint64 + + // RoutesAccepted is the number of routes we sent + RoutesSent uint64 +} diff --git a/protocols/bgp/metrics/bgp_metrics.go b/protocols/bgp/metrics/bgp_metrics.go new file mode 100644 index 00000000..f6ed8497 --- /dev/null +++ b/protocols/bgp/metrics/bgp_metrics.go @@ -0,0 +1,7 @@ +package metrics + +// BGPMetrics provides metrics for a single BGP server instance +type BGPMetrics struct { + // Peers is the collection of per peer metrics + Peers []*BGPPeerMetrics +} diff --git a/protocols/bgp/metrics/bgp_peer_metrics.go b/protocols/bgp/metrics/bgp_peer_metrics.go new file mode 100644 index 00000000..4c70b843 --- /dev/null +++ b/protocols/bgp/metrics/bgp_peer_metrics.go @@ -0,0 +1,40 @@ +package metrics + +import ( + "time" + + bnet "github.com/bio-routing/bio-rd/net" +) + +// BGPPeerMetrics provides metrics for one BGP session +type BGPPeerMetrics struct { + // IP is the remote IP of the peer + IP bnet.IP + + // ASN is the ASN of the peer + ASN uint32 + + // LocalASN is our local ASN + LocalASN uint32 + + // VRF is the name of the VRF the peer is configured in + VRF string + + // Since is the time the session was established + Since time.Time + + // State of the BGP session (Down = 0, Idle = 1, Connect = 2, Active = 3, OpenSent = 4, OpenConfirm = 5, Established = 6) + State uint8 + + // Up returns if the session is established + Up bool + + // UpdatesReceived is the number of update messages received on this session + UpdatesReceived uint64 + + // UpdatesReceived is the number of update messages we sent on this session + UpdatesSent uint64 + + // AddressFamilies provides metrics on AFI/SAFI level + AddressFamilies []*BGPAddressFamilyMetrics +} diff --git a/protocols/bgp/server/fsm.go b/protocols/bgp/server/fsm.go index 115a1eaf..b52bb100 100644 --- a/protocols/bgp/server/fsm.go +++ b/protocols/bgp/server/fsm.go @@ -22,6 +22,13 @@ const ( AutomaticStartWithPassiveTcpEstablishment = 5 AutomaticStop = 8 Cease = 100 + stateNameIdle = "idle" + stateNameConnect = "connect" + stateNameActive = "active" + stateNameOpenSent = "openSent" + stateNameOpenConfirm = "openConfirm" + stateNameEstablished = "established" + stateNameCease = "cease" ) type state interface { @@ -70,6 +77,9 @@ type FSM struct { reason string active bool + establishedTime time.Time + counters fsmCounters + connectionCancelFunc context.CancelFunc } @@ -100,6 +110,7 @@ func newFSM(peer *peer) *FSM { msgRecvCh: make(chan []byte), msgRecvFailCh: make(chan error), stopMsgRecvCh: make(chan struct{}), + counters: fsmCounters{}, } if peer.ipv4 != nil { @@ -162,10 +173,14 @@ func (fsm *FSM) run() { }).Info("FSM: Neighbor state change") } - if newState == "cease" { + if newState == stateNameCease { return } + if oldState != newState && newState == stateNameEstablished { + fsm.establishedTime = time.Now() + } + fsm.stateMu.Lock() fsm.state = next fsm.stateMu.Unlock() @@ -183,19 +198,19 @@ func (fsm *FSM) cancelRunningGoRoutines() { func stateName(s state) string { switch s.(type) { case *idleState: - return "idle" + return stateNameIdle case *connectState: - return "connect" + return stateNameConnect case *activeState: - return "active" + return stateNameActive case *openSentState: - return "openSent" + return stateNameOpenSent case *openConfirmState: - return "openConfirm" + return stateNameOpenConfirm case *establishedState: - return "established" + return stateNameEstablished case *ceaseState: - return "cease" + return stateNameCease default: panic(fmt.Sprintf("Unknown state: %v", s)) } diff --git a/protocols/bgp/server/fsm_counters.go b/protocols/bgp/server/fsm_counters.go new file mode 100644 index 00000000..944719d5 --- /dev/null +++ b/protocols/bgp/server/fsm_counters.go @@ -0,0 +1,11 @@ +package server + +type fsmCounters struct { + updatesReceived uint64 + updatesSent uint64 +} + +func (c *fsmCounters) reset() { + c.updatesReceived = 0 + c.updatesSent = 0 +} diff --git a/protocols/bgp/server/fsm_established.go b/protocols/bgp/server/fsm_established.go index 31526051..3722bd33 100644 --- a/protocols/bgp/server/fsm_established.go +++ b/protocols/bgp/server/fsm_established.go @@ -4,6 +4,7 @@ import ( "bytes" "fmt" "net" + "sync/atomic" "time" bnet "github.com/bio-routing/bio-rd/net" @@ -105,6 +106,8 @@ func (s *establishedState) uninit() { if s.fsm.ipv6Unicast != nil { s.fsm.ipv6Unicast.dispose() } + + s.fsm.counters.reset() } func (s *establishedState) manualStop() (state, string) { @@ -192,6 +195,8 @@ func (s *establishedState) notification() (state, string) { } func (s *establishedState) update(u *packet.BGPUpdate) (state, string) { + atomic.AddUint64(&s.fsm.counters.updatesReceived, 1) + if s.fsm.holdTime != 0 { s.fsm.updateLastUpdateOrKeepalive() } diff --git a/protocols/bgp/server/metrics_service.go b/protocols/bgp/server/metrics_service.go new file mode 100644 index 00000000..1f1fa90b --- /dev/null +++ b/protocols/bgp/server/metrics_service.go @@ -0,0 +1,100 @@ +package server + +import ( + "github.com/bio-routing/bio-rd/protocols/bgp/metrics" +) + +const ( + stateDown = 0 + stateIdle = 1 + stateConnect = 2 + stateActive = 3 + stateOpenSent = 4 + stateOpenConfirm = 5 + stateEstablished = 6 +) + +type metricsService struct { + server *bgpServer +} + +func (b *metricsService) metrics() *metrics.BGPMetrics { + return &metrics.BGPMetrics{ + Peers: b.peerMetrics(), + } +} + +func (b *metricsService) peerMetrics() []*metrics.BGPPeerMetrics { + peers := make([]*metrics.BGPPeerMetrics, 0) + + for _, peer := range b.server.peers.list() { + m := b.metricsForPeer(peer) + peers = append(peers, m) + } + + return peers +} + +func (b *metricsService) metricsForPeer(peer *peer) *metrics.BGPPeerMetrics { + m := &metrics.BGPPeerMetrics{ + ASN: peer.peerASN, + LocalASN: peer.localASN, + IP: peer.addr, + AddressFamilies: make([]*metrics.BGPAddressFamilyMetrics, 0), + VRF: peer.vrf.Name(), + } + + var fsms = peer.fsms + if len(fsms) == 0 { + return m + } + + fsm := fsms[0] + m.State = b.statusFromFSM(fsm) + m.Up = m.State == stateEstablished + + if m.Up { + m.Since = fsm.establishedTime + } + + m.UpdatesReceived = fsm.counters.updatesReceived + m.UpdatesSent = fsm.counters.updatesSent + + if peer.ipv4 != nil { + m.AddressFamilies = append(m.AddressFamilies, b.metricsForFamily(fsm.ipv4Unicast)) + } + + if peer.ipv6 != nil { + m.AddressFamilies = append(m.AddressFamilies, b.metricsForFamily(fsm.ipv6Unicast)) + } + + return m +} + +func (b *metricsService) metricsForFamily(family *fsmAddressFamily) *metrics.BGPAddressFamilyMetrics { + return &metrics.BGPAddressFamilyMetrics{ + AFI: family.afi, + SAFI: family.safi, + RoutesReceived: uint64(family.adjRIBIn.RouteCount()), + RoutesSent: uint64(family.adjRIBOut.RouteCount()), + } +} + +func (b *metricsService) statusFromFSM(fsm *FSM) uint8 { + switch fsm.state.(type) { + case *idleState: + return stateIdle + case *connectState: + return stateConnect + case *activeState: + return stateActive + case *openSentState: + return stateOpenSent + case *openConfirmState: + return stateOpenConfirm + case *establishedState: + return stateEstablished + } + + return stateDown +} diff --git a/protocols/bgp/server/metrics_service_test.go b/protocols/bgp/server/metrics_service_test.go new file mode 100644 index 00000000..f98ec61e --- /dev/null +++ b/protocols/bgp/server/metrics_service_test.go @@ -0,0 +1,299 @@ +package server + +import ( + "testing" + "time" + + "github.com/bio-routing/bio-rd/protocols/bgp/packet" + "github.com/bio-routing/bio-rd/routingtable" + "github.com/bio-routing/bio-rd/routingtable/vrf" + "github.com/stretchr/testify/assert" + + bnet "github.com/bio-routing/bio-rd/net" + "github.com/bio-routing/bio-rd/protocols/bgp/metrics" +) + +func TestMetrics(t *testing.T) { + vrf, _ := vrf.New("inet.0") + establishedTime := time.Now() + + tests := []struct { + name string + peer *peer + withoutFSM bool + state state + updatesReceived uint64 + updatesSent uint64 + ipv4RoutesReceived int64 + ipv4RoutesSent int64 + ipv6RoutesReceived int64 + ipv6RoutesSent int64 + expected *metrics.BGPMetrics + }{ + { + name: "Established", + peer: &peer{ + peerASN: 202739, + localASN: 201701, + addr: bnet.IPv4(100), + ipv4: &peerAddressFamily{}, + ipv6: &peerAddressFamily{}, + vrf: vrf, + }, + state: &establishedState{}, + updatesReceived: 3, + updatesSent: 4, + ipv4RoutesReceived: 5, + ipv4RoutesSent: 6, + ipv6RoutesReceived: 7, + ipv6RoutesSent: 8, + expected: &metrics.BGPMetrics{ + Peers: []*metrics.BGPPeerMetrics{ + { + IP: bnet.IPv4(100), + ASN: 202739, + LocalASN: 201701, + UpdatesReceived: 3, + UpdatesSent: 4, + VRF: "inet.0", + Up: true, + State: stateEstablished, + Since: establishedTime, + AddressFamilies: []*metrics.BGPAddressFamilyMetrics{ + { + AFI: packet.IPv4AFI, + SAFI: packet.UnicastSAFI, + RoutesReceived: 5, + RoutesSent: 6, + }, + { + AFI: packet.IPv6AFI, + SAFI: packet.UnicastSAFI, + RoutesReceived: 7, + RoutesSent: 8, + }, + }, + }, + }, + }, + }, + { + name: "Idle", + peer: &peer{ + peerASN: 202739, + localASN: 201701, + addr: bnet.IPv4(100), + ipv4: &peerAddressFamily{}, + ipv6: &peerAddressFamily{}, + vrf: vrf, + }, + state: &idleState{}, + expected: &metrics.BGPMetrics{ + Peers: []*metrics.BGPPeerMetrics{ + { + IP: bnet.IPv4(100), + ASN: 202739, + LocalASN: 201701, + VRF: "inet.0", + State: stateIdle, + AddressFamilies: []*metrics.BGPAddressFamilyMetrics{ + { + AFI: packet.IPv4AFI, + SAFI: packet.UnicastSAFI, + }, + { + AFI: packet.IPv6AFI, + SAFI: packet.UnicastSAFI, + }, + }, + }, + }, + }, + }, + { + name: "Active", + peer: &peer{ + peerASN: 202739, + localASN: 201701, + addr: bnet.IPv4(100), + ipv4: &peerAddressFamily{}, + ipv6: &peerAddressFamily{}, + vrf: vrf, + }, + state: &activeState{}, + expected: &metrics.BGPMetrics{ + Peers: []*metrics.BGPPeerMetrics{ + { + IP: bnet.IPv4(100), + ASN: 202739, + LocalASN: 201701, + VRF: "inet.0", + State: stateActive, + AddressFamilies: []*metrics.BGPAddressFamilyMetrics{ + { + AFI: packet.IPv4AFI, + SAFI: packet.UnicastSAFI, + }, + { + AFI: packet.IPv6AFI, + SAFI: packet.UnicastSAFI, + }, + }, + }, + }, + }, + }, + { + name: "OpenSent", + peer: &peer{ + peerASN: 202739, + localASN: 201701, + addr: bnet.IPv4(100), + ipv4: &peerAddressFamily{}, + ipv6: &peerAddressFamily{}, + vrf: vrf, + }, + state: &openSentState{}, + expected: &metrics.BGPMetrics{ + Peers: []*metrics.BGPPeerMetrics{ + { + IP: bnet.IPv4(100), + ASN: 202739, + LocalASN: 201701, + VRF: "inet.0", + State: stateOpenSent, + AddressFamilies: []*metrics.BGPAddressFamilyMetrics{ + { + AFI: packet.IPv4AFI, + SAFI: packet.UnicastSAFI, + }, + { + AFI: packet.IPv6AFI, + SAFI: packet.UnicastSAFI, + }, + }, + }, + }, + }, + }, + { + name: "OpenConfirm", + peer: &peer{ + peerASN: 202739, + localASN: 201701, + addr: bnet.IPv4(100), + ipv4: &peerAddressFamily{}, + ipv6: &peerAddressFamily{}, + vrf: vrf, + }, + state: &openConfirmState{}, + expected: &metrics.BGPMetrics{ + Peers: []*metrics.BGPPeerMetrics{ + { + IP: bnet.IPv4(100), + ASN: 202739, + LocalASN: 201701, + VRF: "inet.0", + State: stateOpenConfirm, + AddressFamilies: []*metrics.BGPAddressFamilyMetrics{ + { + AFI: packet.IPv4AFI, + SAFI: packet.UnicastSAFI, + }, + { + AFI: packet.IPv6AFI, + SAFI: packet.UnicastSAFI, + }, + }, + }, + }, + }, + }, + { + name: "Connect", + peer: &peer{ + peerASN: 202739, + localASN: 201701, + addr: bnet.IPv4(100), + ipv4: &peerAddressFamily{}, + ipv6: &peerAddressFamily{}, + vrf: vrf, + }, + state: &connectState{}, + expected: &metrics.BGPMetrics{ + Peers: []*metrics.BGPPeerMetrics{ + { + IP: bnet.IPv4(100), + ASN: 202739, + LocalASN: 201701, + VRF: "inet.0", + State: stateConnect, + AddressFamilies: []*metrics.BGPAddressFamilyMetrics{ + { + AFI: packet.IPv4AFI, + SAFI: packet.UnicastSAFI, + }, + { + AFI: packet.IPv6AFI, + SAFI: packet.UnicastSAFI, + }, + }, + }, + }, + }, + }, + { + name: "without fsm", + withoutFSM: true, + peer: &peer{ + peerASN: 202739, + localASN: 201701, + addr: bnet.IPv4(100), + ipv4: &peerAddressFamily{}, + ipv6: &peerAddressFamily{}, + vrf: vrf, + }, + expected: &metrics.BGPMetrics{ + Peers: []*metrics.BGPPeerMetrics{ + { + IP: bnet.IPv4(100), + ASN: 202739, + LocalASN: 201701, + VRF: "inet.0", + AddressFamilies: []*metrics.BGPAddressFamilyMetrics{}, + }, + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + if !test.withoutFSM { + fsm := newFSM(test.peer) + test.peer.fsms = append(test.peer.fsms, fsm) + + fsm.state = test.state + fsm.counters.updatesReceived = test.updatesReceived + fsm.counters.updatesSent = test.updatesSent + + fsm.ipv4Unicast.adjRIBIn = &routingtable.RTMockClient{FakeRouteCount: test.ipv4RoutesReceived} + fsm.ipv4Unicast.adjRIBOut = &routingtable.RTMockClient{FakeRouteCount: test.ipv4RoutesSent} + fsm.ipv6Unicast.adjRIBIn = &routingtable.RTMockClient{FakeRouteCount: test.ipv6RoutesReceived} + fsm.ipv6Unicast.adjRIBOut = &routingtable.RTMockClient{FakeRouteCount: test.ipv6RoutesSent} + + fsm.establishedTime = establishedTime + } + + s := newBgpServer() + s.peers.add(test.peer) + + actual, err := s.Metrics() + if err != nil { + t.Fatalf("unecpected error: %v", err) + } + + assert.Equal(t, test.expected, actual) + }) + } +} diff --git a/protocols/bgp/server/peer.go b/protocols/bgp/server/peer.go index f537b58e..2659d1e4 100644 --- a/protocols/bgp/server/peer.go +++ b/protocols/bgp/server/peer.go @@ -2,6 +2,7 @@ package server import ( "fmt" + "github.com/bio-routing/bio-rd/routingtable/vrf" "sync" "time" @@ -36,6 +37,7 @@ type peer struct { ipv4MultiProtocolAdvertised bool clusterID uint32 + vrf *vrf.VRF ipv4 *peerAddressFamily ipv6 *peerAddressFamily } @@ -164,6 +166,7 @@ func newPeer(c config.Peer, server *bgpServer) (*peer, error) { routeServerClient: c.RouteServerClient, routeReflectorClient: c.RouteReflectorClient, clusterID: c.RouteReflectorClusterID, + vrf: c.VRF, } if c.IPv4 != nil { diff --git a/protocols/bgp/server/server.go b/protocols/bgp/server/server.go index d3d0a674..980e922a 100644 --- a/protocols/bgp/server/server.go +++ b/protocols/bgp/server/server.go @@ -1,6 +1,7 @@ package server import ( + "fmt" "net" "github.com/bio-routing/bio-rd/routingtable/adjRIBOut" @@ -9,6 +10,7 @@ import ( "github.com/bio-routing/bio-rd/config" bnet "github.com/bio-routing/bio-rd/net" + "github.com/bio-routing/bio-rd/protocols/bgp/metrics" bnetutils "github.com/bio-routing/bio-rd/util/net" "github.com/pkg/errors" log "github.com/sirupsen/logrus" @@ -24,21 +26,31 @@ type bgpServer struct { peers *peerManager routerID uint32 localASN uint32 + metrics *metricsService } type BGPServer interface { RouterID() uint32 Start(*config.Global) error AddPeer(config.Peer) error + Metrics() (*metrics.BGPMetrics, error) GetRIBIn(peerIP bnet.IP, afi uint16, safi uint8) *adjRIBIn.AdjRIBIn GetRIBOut(peerIP bnet.IP, afi uint16, safi uint8) *adjRIBOut.AdjRIBOut ConnectMockPeer(peer config.Peer, con net.Conn) } +// NewBgpServer creates a new instance of bgpServer func NewBgpServer() BGPServer { - return &bgpServer{ + return newBgpServer() +} + +func newBgpServer() *bgpServer { + server := &bgpServer{ peers: newPeerManager(), } + + server.metrics = &metricsService{server} + return server } func (b *bgpServer) RouterID() uint32 { @@ -163,3 +175,11 @@ func (b *bgpServer) AddPeer(c config.Peer) error { return nil } + +func (b *bgpServer) Metrics() (*metrics.BGPMetrics, error) { + if b.metrics == nil { + return nil, fmt.Errorf("Server not started yet") + } + + return b.metrics.metrics(), nil +} diff --git a/protocols/bgp/server/update_sender.go b/protocols/bgp/server/update_sender.go index c43d1778..1b7bca8c 100644 --- a/protocols/bgp/server/update_sender.go +++ b/protocols/bgp/server/update_sender.go @@ -5,6 +5,7 @@ import ( "fmt" "io" "sync" + "sync/atomic" "time" bnet "github.com/bio-routing/bio-rd/net" @@ -180,6 +181,7 @@ func (u *UpdateSender) sendUpdates(pathAttrs *packet.PathAttribute, updatePrefix if err != nil { log.Errorf("Failed to serialize and send: %v", err) } + atomic.AddUint64(&u.fsm.counters.updatesSent, 1) } } diff --git a/routingtable/locRIB/loc_rib.go b/routingtable/locRIB/loc_rib.go index b7a630ed..3503aacf 100644 --- a/routingtable/locRIB/loc_rib.go +++ b/routingtable/locRIB/loc_rib.go @@ -48,12 +48,9 @@ func (a *LocRIB) GetContributingASNs() *routingtable.ContributingASNs { return a.contributingASNs } -//Count routes from the LocRIP +// Count routes from the LocRIB func (a *LocRIB) Count() uint64 { - a.mu.RLock() - defer a.mu.RUnlock() - - return uint64(len(a.rt.Dump())) + return uint64(a.rt.GetRouteCount()) } // Dump dumps the RIB diff --git a/routingtable/mock_client.go b/routingtable/mock_client.go index 25a26c40..6538a2ce 100644 --- a/routingtable/mock_client.go +++ b/routingtable/mock_client.go @@ -13,7 +13,8 @@ type RemovePathParams struct { } type RTMockClient struct { - removed []*RemovePathParams + removed []*RemovePathParams + FakeRouteCount int64 } func NewRTMockClient() *RTMockClient { @@ -67,5 +68,5 @@ func (m *RTMockClient) RemovePath(pfx net.Prefix, p *route.Path) bool { } func (m *RTMockClient) RouteCount() int64 { - return 0 + return m.FakeRouteCount } diff --git a/routingtable/vrf/metrics.go b/routingtable/vrf/metrics.go new file mode 100644 index 00000000..b8d43ee4 --- /dev/null +++ b/routingtable/vrf/metrics.go @@ -0,0 +1,37 @@ +package vrf + +import ( + "github.com/bio-routing/bio-rd/routingtable/vrf/metrics" +) + +// Metrics returns metrics for all VRFs +func Metrics() []*metrics.VRFMetrics { + vrfs := globalRegistry.list() + + m := make([]*metrics.VRFMetrics, len(vrfs)) + i := 0 + for _, v := range vrfs { + m[i] = metricsForVRF(v) + i++ + } + + return m +} + +func metricsForVRF(v *VRF) *metrics.VRFMetrics { + m := &metrics.VRFMetrics{ + Name: v.Name(), + RIBs: make([]*metrics.RIBMetrics, 0), + } + + for family, rib := range v.ribs { + m.RIBs = append(m.RIBs, &metrics.RIBMetrics{ + Name: v.nameForRIB(rib), + AFI: family.afi, + SAFI: family.safi, + RouteCount: rib.Count(), + }) + } + + return m +} diff --git a/routingtable/vrf/metrics/rib_metrics.go b/routingtable/vrf/metrics/rib_metrics.go new file mode 100644 index 00000000..6758d771 --- /dev/null +++ b/routingtable/vrf/metrics/rib_metrics.go @@ -0,0 +1,16 @@ +package metrics + +// RIBMetrics represents metrics of a RIB in a VRF +type RIBMetrics struct { + // Name of the RIB + Name string + + // AFI is the identifier for the address family + AFI uint16 + + // SAFI is the identifier for the sub address family + SAFI uint8 + + // Number of routes in the RIB + RouteCount uint64 +} diff --git a/routingtable/vrf/metrics/vrf_metrics.go b/routingtable/vrf/metrics/vrf_metrics.go new file mode 100644 index 00000000..3c5ba2c0 --- /dev/null +++ b/routingtable/vrf/metrics/vrf_metrics.go @@ -0,0 +1,10 @@ +package metrics + +// VRFMetrics represents a collection of metrics of one VRF +type VRFMetrics struct { + // Name of the VRF + Name string + + // RIBs returns the RIB specific metrics + RIBs []*RIBMetrics +} diff --git a/routingtable/vrf/metrics_test.go b/routingtable/vrf/metrics_test.go new file mode 100644 index 00000000..b946699e --- /dev/null +++ b/routingtable/vrf/metrics_test.go @@ -0,0 +1,83 @@ +package vrf + +import ( + "sort" + "testing" + + "github.com/stretchr/testify/assert" + + bnet "github.com/bio-routing/bio-rd/net" + "github.com/bio-routing/bio-rd/route" + "github.com/bio-routing/bio-rd/routingtable/vrf/metrics" +) + +func TestMetrics(t *testing.T) { + green, err := New("green") + if err != nil { + t.Fatal(err) + } + green.IPv4UnicastRIB().AddPath(bnet.NewPfx(bnet.IPv4FromOctets(8, 0, 0, 0), 8), &route.Path{}) + green.IPv4UnicastRIB().AddPath(bnet.NewPfx(bnet.IPv4FromOctets(8, 0, 0, 0), 16), &route.Path{}) + green.IPv6UnicastRIB().AddPath(bnet.NewPfx(bnet.IPv6FromBlocks(0x2001, 0x678, 0x1e0, 0, 0, 0, 0, 0), 48), &route.Path{}) + + red, err := New("red") + if err != nil { + t.Fatal(err) + } + red.IPv6UnicastRIB().AddPath(bnet.NewPfx(bnet.IPv6FromBlocks(0x2001, 0x678, 0x1e0, 0x100, 0, 0, 0, 0), 64), &route.Path{}) + red.IPv6UnicastRIB().AddPath(bnet.NewPfx(bnet.IPv6FromBlocks(0x2001, 0x678, 0x1e0, 0x200, 0, 0, 0, 0), 64), &route.Path{}) + + expected := []*metrics.VRFMetrics{ + { + Name: "green", + RIBs: []*metrics.RIBMetrics{ + { + Name: "inet.0", + AFI: afiIPv4, + SAFI: safiUnicast, + RouteCount: 2, + }, + { + Name: "inet6.0", + AFI: afiIPv6, + SAFI: safiUnicast, + RouteCount: 1, + }, + }, + }, + { + Name: "red", + RIBs: []*metrics.RIBMetrics{ + { + Name: "inet.0", + AFI: afiIPv4, + SAFI: safiUnicast, + RouteCount: 0, + }, + { + Name: "inet6.0", + AFI: afiIPv6, + SAFI: safiUnicast, + RouteCount: 2, + }, + }, + }, + } + + actual := Metrics() + sortResult(actual) + + assert.Equal(t, expected, actual) +} + +func sortResult(m []*metrics.VRFMetrics) { + sort.Slice(m, func(i, j int) bool { + return m[i].Name < m[j].Name + }) + + for _, v := range m { + sort.Slice(v.RIBs, func(i, j int) bool { + return m[i].Name < m[j].Name + }) + } +} diff --git a/routingtable/vrf/vrf.go b/routingtable/vrf/vrf.go index eb9d4a3c..bdc29058 100644 --- a/routingtable/vrf/vrf.go +++ b/routingtable/vrf/vrf.go @@ -85,6 +85,7 @@ func (v *VRF) IPv6UnicastRIB() *locRIB.LocRIB { return v.ribForAddressFamily(addressFamily{afi: afiIPv6, safi: safiUnicast}) } +// Name is the name of the VRF func (v *VRF) Name() string { return v.name } @@ -108,3 +109,13 @@ func (v *VRF) RIBByName(name string) (rib *locRIB.LocRIB, found bool) { rib, found = v.ribNames[name] return rib, found } + +func (v *VRF) nameForRIB(rib *locRIB.LocRIB) string { + for name, r := range v.ribNames { + if r == rib { + return name + } + } + + return "" +} diff --git a/routingtable/vrf/vrf_registry.go b/routingtable/vrf/vrf_registry.go index 15cfc143..0fae0720 100644 --- a/routingtable/vrf/vrf_registry.go +++ b/routingtable/vrf/vrf_registry.go @@ -41,3 +41,17 @@ func (r *vrfRegistry) unregisterVRF(v *VRF) { delete(r.vrfs, v.name) } + +func (r *vrfRegistry) list() []*VRF { + r.mu.Lock() + defer r.mu.Unlock() + + l := make([]*VRF, len(r.vrfs)) + i := 0 + for _, v := range r.vrfs { + l[i] = v + i++ + } + + return l +} -- GitLab