CI / changes (push) Successful in 9s
CI / commitlint (push) Has been skipped
CI / openapi (push) Successful in 24s
CI / web (push) Successful in 29s
CI / go (push) Successful in 43s
CI / bird2 (push) Successful in 15s
CI / release (push) Successful in 3m29s
- Introduced new schemas for `SpeakerLiveStatus`, `BgpSessionLive`, and `LiveSpeakerPoll` in OpenAPI documentation to support live status queries. - Enhanced the `/v1/speakers` endpoint to include a `live` query parameter, allowing retrieval of real-time speaker and BGP status. - Updated the HTTP API to collect and return live status data for speakers, improving monitoring capabilities. - Modified frontend components to display live status information, enhancing user visibility into speaker health and BGP session states. - Added a new endpoint `/v1/bird/status` for retrieving the local BIRD status, further enriching the network monitoring features.
130 lines
3.3 KiB
Go
130 lines
3.3 KiB
Go
package httpapi
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"evobgp/internal/birdfmt"
|
|
"evobgp/internal/nodedispatch"
|
|
"evobgp/internal/store"
|
|
)
|
|
|
|
func countBGPSessions(sessions []birdfmt.BGPSession) (total, established int) {
|
|
total = len(sessions)
|
|
for _, s := range sessions {
|
|
if strings.EqualFold(strings.TrimSpace(s.State), "Established") {
|
|
established++
|
|
}
|
|
}
|
|
return total, established
|
|
}
|
|
|
|
func speakerLiveStatusJSON(sp *store.Speaker, view speakerBGPLive, health *nodedispatch.AgentHealthResult) map[string]any {
|
|
total, established := countBGPSessions(view.Sessions)
|
|
m := map[string]any{
|
|
"label": view.Label,
|
|
"bgp_poll_ok": view.Error == "",
|
|
"bgp_sessions_total": total,
|
|
"bgp_established": established,
|
|
}
|
|
if view.Error != "" {
|
|
m["bgp_poll_error"] = view.Error
|
|
}
|
|
if health != nil {
|
|
m["agent_ok"] = health.OK
|
|
if health.Error != "" {
|
|
m["agent_error"] = health.Error
|
|
}
|
|
if health.LastSyncAt != "" {
|
|
m["agent_last_sync_at"] = health.LastSyncAt
|
|
}
|
|
if health.LastAppliedRevisionID != "" {
|
|
m["agent_last_applied_revision_id"] = health.LastAppliedRevisionID
|
|
}
|
|
} else if sp != nil && strings.EqualFold(strings.TrimSpace(sp.Role), "master") {
|
|
m["agent_ok"] = view.Error == ""
|
|
if view.Error != "" {
|
|
m["agent_error"] = view.Error
|
|
}
|
|
} else if sp != nil && store.SpeakerNeedsRemoteDispatch(sp.Role, store.ParseSpeakerMeta(sp.MetaJSON)) {
|
|
m["agent_ok"] = false
|
|
m["agent_error"] = "agent health not polled"
|
|
}
|
|
if len(view.Sessions) > 0 {
|
|
sess := make([]map[string]any, 0, len(view.Sessions))
|
|
for _, s := range view.Sessions {
|
|
row := map[string]any{
|
|
"name": s.Name,
|
|
"state": s.State,
|
|
}
|
|
if strings.TrimSpace(s.Neighbor) != "" {
|
|
row["neighbor"] = s.Neighbor
|
|
}
|
|
sess = append(sess, row)
|
|
}
|
|
m["sessions"] = sess
|
|
}
|
|
return m
|
|
}
|
|
|
|
func (s *Server) collectSpeakerLiveStatus(ctx context.Context, tenantID string, fresh bool, speakers []*store.Speaker) map[string]map[string]any {
|
|
views := s.collectSpeakerBGPLive(ctx, tenantID, fresh)
|
|
viewByID := make(map[string]speakerBGPLive, len(views))
|
|
for _, v := range views {
|
|
if v.SpeakerID != "" {
|
|
viewByID[v.SpeakerID] = v
|
|
}
|
|
}
|
|
|
|
opts := nodedispatch.Options{Timeout: 8 * time.Second}
|
|
type healthWrap struct {
|
|
id string
|
|
h nodedispatch.AgentHealthResult
|
|
}
|
|
healthCh := make(chan healthWrap, len(speakers))
|
|
var wg sync.WaitGroup
|
|
for _, sp := range speakers {
|
|
if sp == nil {
|
|
continue
|
|
}
|
|
meta := store.ParseSpeakerMeta(sp.MetaJSON)
|
|
if !store.SpeakerNeedsRemoteDispatch(sp.Role, meta) {
|
|
continue
|
|
}
|
|
wg.Add(1)
|
|
go func(speaker *store.Speaker) {
|
|
defer wg.Done()
|
|
healthCh <- healthWrap{
|
|
id: speaker.ID,
|
|
h: nodedispatch.FetchAgentHealth(ctx, speaker, opts),
|
|
}
|
|
}(sp)
|
|
}
|
|
wg.Wait()
|
|
close(healthCh)
|
|
healthByID := make(map[string]nodedispatch.AgentHealthResult, len(speakers))
|
|
for hw := range healthCh {
|
|
healthByID[hw.id] = hw.h
|
|
}
|
|
|
|
out := make(map[string]map[string]any, len(speakers))
|
|
for _, sp := range speakers {
|
|
if sp == nil {
|
|
continue
|
|
}
|
|
view, ok := viewByID[sp.ID]
|
|
if !ok {
|
|
view = speakerBGPLive{SpeakerID: sp.ID, Label: speakerDisplayLabel(sp)}
|
|
}
|
|
var hp *nodedispatch.AgentHealthResult
|
|
if h, ok := healthByID[sp.ID]; ok {
|
|
hCopy := h
|
|
hp = &hCopy
|
|
}
|
|
out[sp.ID] = speakerLiveStatusJSON(sp, view, hp)
|
|
}
|
|
return out
|
|
}
|