feat: add live BGP protocol state tracking to peer listing
CI / changes (push) Successful in 6s
CI / openapi (push) Has been skipped
CI / go (push) Successful in 39s
CI / docker-web (deploy/docker/evobgp-web/Dockerfile, , evobgp-web) (push) Has been skipped
CI / docker-web (deploy/docker/evobgp-web/Dockerfile, evobgp-all, evobgp-web-all) (push) Has been skipped
CI / docker-bird (push) Has been skipped
CI / bird2 (push) Successful in 13s
CI / docker-go-prime (push) Successful in 24s
CI / docker-go (deploy/docker/evobgp-agent/Dockerfile, , evobgp-agent) (push) Successful in 59s
CI / docker-go (evobgp-all, 1, deploy/docker/gobinary/Dockerfile, , evobgp-all) (push) Successful in 6m19s
CI / docker-go (evobgp-api, 1, deploy/docker/gobinary/Dockerfile, , evobgp-api) (push) Successful in 1m28s
CI / docker-go (evobgp-deploy, 0, deploy/docker/gobinary/Dockerfile, , evobgp-deploy) (push) Successful in 1m22s
CI / docker-go (evobgp-ingest, 0, deploy/docker/gobinary/Dockerfile, , evobgp-ingest) (push) Successful in 1m25s
CI / docker-go (evobgp-node, 0, deploy/docker/gobinary/Dockerfile, , evobgp-node) (push) Successful in 1m10s
CI / docker-go (evobgp-render, 0, deploy/docker/gobinary/Dockerfile, , evobgp-render) (push) Successful in 1m23s
CI / docker-go (evobgp-scheduler, 0, deploy/docker/gobinary/Dockerfile, , evobgp-scheduler) (push) Successful in 1m22s

Enhanced the peer listing API to include live BGP session states by integrating a new function to retrieve protocol states from the BIRD socket. Added parsing logic for BGP protocol states and updated the peer JSON response to include session state information. Introduced unit tests for the new parsing functionality to ensure accuracy in state retrieval.
This commit is contained in:
Denozordec
2026-04-09 15:39:01 +07:00
parent 6107c3eb87
commit 8a50ba44b3
2 changed files with 91 additions and 1 deletions
+75 -1
View File
@@ -319,15 +319,89 @@ func (s *Server) handleListPeers(w http.ResponseWriter, r *http.Request) {
return return
} }
peers := s.store.ListPeers(a.TenantID) peers := s.store.ListPeers(a.TenantID)
liveStates := s.liveBGPProtocolStates(r.Context())
items := make([]map[string]any, 0, len(peers)) items := make([]map[string]any, 0, len(peers))
for _, p := range peers { for _, p := range peers {
items = append(items, peerJSON(p)) row := peerJSON(p)
if st, ok := liveStates[peerProtocolNameForID(p.ID)]; ok && strings.TrimSpace(st) != "" {
row["session_state"] = strings.TrimSpace(st)
}
items = append(items, row)
} }
writeJSON(w, http.StatusOK, map[string]any{ writeJSON(w, http.StatusOK, map[string]any{
"items": items, "next_cursor": nil, "has_more": false, "items": items, "next_cursor": nil, "has_more": false,
}) })
} }
func (s *Server) liveBGPProtocolStates(ctx context.Context) map[string]string {
sock := strings.TrimSpace(os.Getenv("EVOBGP_BIRDC_SOCKET"))
if sock == "" {
return map[string]string{}
}
out, err := birdfmt.ShowProtocols(ctx, sock, strings.TrimSpace(os.Getenv("EVOBGP_BIRDC_BIN")))
if err != nil {
return map[string]string{}
}
return parseBGPProtocolStates(out)
}
// parseBGPProtocolStates parses `birdc show protocols all` summary rows into protocol_name -> state.
func parseBGPProtocolStates(output string) map[string]string {
out := make(map[string]string)
for _, raw := range strings.Split(output, "\n") {
line := strings.TrimSpace(raw)
if line == "" {
continue
}
low := strings.ToLower(line)
if strings.HasPrefix(low, "bird ") || strings.HasPrefix(low, "name ") || strings.HasPrefix(low, "table ") {
continue
}
fields := strings.Fields(line)
if len(fields) < 4 {
continue
}
if !strings.EqualFold(fields[1], "BGP") {
continue
}
state := extractBGPSessionState(line)
if state == "" {
state = fields[3]
}
out[fields[0]] = state
}
return out
}
func extractBGPSessionState(line string) string {
known := []string{
"Established",
"Idle",
"Connect",
"Active",
"OpenSent",
"OpenConfirm",
}
for _, st := range known {
if strings.Contains(line, st) {
return st
}
}
return ""
}
// peerProtocolNameForID must stay in sync with pipeline peer protocol naming.
func peerProtocolNameForID(peerID string) string {
s := strings.ReplaceAll(strings.TrimSpace(peerID), "-", "")
if len(s) > 16 {
s = s[:16]
}
if s == "" {
s = "x"
}
return "evobgp_p_" + s
}
func (s *Server) handleListSpeakers(w http.ResponseWriter, r *http.Request) { func (s *Server) handleListSpeakers(w http.ResponseWriter, r *http.Request) {
a, ok := authFromContext(r.Context()) a, ok := authFromContext(r.Context())
if !ok { if !ok {
+16
View File
@@ -341,3 +341,19 @@ func waitJob(t *testing.T, client *http.Client, base, token, jobID string) {
} }
t.Fatalf("job %s did not complete", jobID) t.Fatalf("job %s did not complete", jobID)
} }
func TestParseBGPProtocolStates(t *testing.T) {
raw := `BIRD 2.16.2 ready.
name proto table state since info
device1 Device --- up 2026-04-09
evobgp_p_abcdef01 BGP master up 2026-04-09 Established
evobgp_p_01234567 BGP master start 2026-04-09 Connect
`
got := parseBGPProtocolStates(raw)
if got["evobgp_p_abcdef01"] != "Established" {
t.Fatalf("expected protocol state Established, got %q", got["evobgp_p_abcdef01"])
}
if got["evobgp_p_01234567"] != "Connect" {
t.Fatalf("expected protocol state Connect, got %q", got["evobgp_p_01234567"])
}
}