feat: update BGP community structure and related documentation. Refactor BGP community handling by renaming fields from 'name' and 'kind' to 'community' and 'title' across the codebase. Update OpenAPI specifications, database interactions, and UI components to reflect these changes, enhancing clarity and consistency in community management.
CI / changes (push) Successful in 5s
CI / openapi (push) Successful in 25s
CI / go (push) Successful in 24s
CI / docker-web (deploy/docker/evobgp-web/Dockerfile, , evobgp-web) (push) Successful in 1m1s
CI / docker-web (deploy/docker/evobgp-web/Dockerfile, evobgp-all, evobgp-web-all) (push) Successful in 1m1s
CI / docker-bird (push) Has been skipped
CI / bird2 (push) Successful in 15s
CI / docker-go (evobgp-all, 1, deploy/docker/gobinary/Dockerfile, , evobgp-all) (push) Has been cancelled
CI / docker-go (evobgp-api, 1, deploy/docker/gobinary/Dockerfile, , evobgp-api) (push) Has been cancelled
CI / docker-go (evobgp-deploy, 0, deploy/docker/gobinary/Dockerfile, , evobgp-deploy) (push) Has been cancelled
CI / docker-go (evobgp-ingest, 0, deploy/docker/gobinary/Dockerfile, , evobgp-ingest) (push) Has been cancelled
CI / docker-go (evobgp-node, 0, deploy/docker/gobinary/Dockerfile, , evobgp-node) (push) Has been cancelled
CI / docker-go (evobgp-render, 0, deploy/docker/gobinary/Dockerfile, , evobgp-render) (push) Has been cancelled
CI / docker-go (evobgp-scheduler, 0, deploy/docker/gobinary/Dockerfile, , evobgp-scheduler) (push) Has been cancelled
CI / docker-go (deploy/docker/evobgp-agent/Dockerfile, , evobgp-agent) (push) Successful in 1m1s
CI / changes (push) Successful in 5s
CI / openapi (push) Successful in 25s
CI / go (push) Successful in 24s
CI / docker-web (deploy/docker/evobgp-web/Dockerfile, , evobgp-web) (push) Successful in 1m1s
CI / docker-web (deploy/docker/evobgp-web/Dockerfile, evobgp-all, evobgp-web-all) (push) Successful in 1m1s
CI / docker-bird (push) Has been skipped
CI / bird2 (push) Successful in 15s
CI / docker-go (evobgp-all, 1, deploy/docker/gobinary/Dockerfile, , evobgp-all) (push) Has been cancelled
CI / docker-go (evobgp-api, 1, deploy/docker/gobinary/Dockerfile, , evobgp-api) (push) Has been cancelled
CI / docker-go (evobgp-deploy, 0, deploy/docker/gobinary/Dockerfile, , evobgp-deploy) (push) Has been cancelled
CI / docker-go (evobgp-ingest, 0, deploy/docker/gobinary/Dockerfile, , evobgp-ingest) (push) Has been cancelled
CI / docker-go (evobgp-node, 0, deploy/docker/gobinary/Dockerfile, , evobgp-node) (push) Has been cancelled
CI / docker-go (evobgp-render, 0, deploy/docker/gobinary/Dockerfile, , evobgp-render) (push) Has been cancelled
CI / docker-go (evobgp-scheduler, 0, deploy/docker/gobinary/Dockerfile, , evobgp-scheduler) (push) Has been cancelled
CI / docker-go (deploy/docker/evobgp-agent/Dockerfile, , evobgp-agent) (push) Successful in 1m1s
This commit is contained in:
@@ -576,7 +576,7 @@ func commJSON(x *store.Community) map[string]any {
|
||||
if err := json.Unmarshal([]byte(x.ValueJSON), &v); err != nil {
|
||||
v = x.ValueJSON
|
||||
}
|
||||
return map[string]any{"id": x.ID, "name": x.Name, "kind": x.Kind, "value_json": v}
|
||||
return map[string]any{"id": x.ID, "community": x.Community, "title": x.Title, "value_json": v}
|
||||
}
|
||||
|
||||
func (s *Server) handleGetComm(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
@@ -90,5 +90,5 @@ func communityRouteBody(st store.Backend, tenantID string, cid *string) (string,
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("community %s: %w", id, err)
|
||||
}
|
||||
return birdfmt.RouteCommunityAttrs(c.Kind, c.Name, c.ValueJSON)
|
||||
return birdfmt.RouteCommunityAttrs("", c.Community, c.ValueJSON)
|
||||
}
|
||||
|
||||
@@ -33,6 +33,8 @@ func MaterializedASPrefixKey(asn int64) string {
|
||||
|
||||
// RefreshModule runs ingest (where applicable) for one module, then renders a new revision whose
|
||||
// BIRD materialization includes prefixes from all enabled modules of the tenant (others via live collect).
|
||||
// If the tenant-wide materialized prefix set is unchanged from the latest revision, returns that
|
||||
// revision id and does not insert a duplicate config_revision.
|
||||
func RefreshModule(ctx context.Context, st store.Backend, hc *http.Client, tenantID, moduleID string) (revisionID string, err error) {
|
||||
if hc == nil {
|
||||
hc = http.DefaultClient
|
||||
@@ -50,13 +52,17 @@ func RefreshModule(ctx context.Context, st store.Backend, hc *http.Client, tenan
|
||||
return "", err
|
||||
}
|
||||
|
||||
revisionID = uuid.NewString()
|
||||
parent := parentRevision(st, tenantID, moduleID)
|
||||
agg, err := aggregateTenantPrefixRows(ctx, st, hc, tenantID, moduleID, rows)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
hash := hashAggregatedMaterialization(tenantID, agg)
|
||||
if prev := latestTenantRevision(st, tenantID); prev != nil && prev.ContentHash == hash {
|
||||
return prev.ID, nil
|
||||
}
|
||||
|
||||
revisionID = uuid.NewString()
|
||||
parent := parentRevision(st, tenantID, moduleID)
|
||||
preview, err := buildPreviewFragments(st, tenantID, moduleID, revisionID, agg)
|
||||
if err != nil {
|
||||
return "", err
|
||||
@@ -233,6 +239,15 @@ func parentRevision(st store.Backend, tenantID, moduleID string) *string {
|
||||
return &id
|
||||
}
|
||||
|
||||
// latestTenantRevision is the newest config_revision for the tenant (any module), or nil.
|
||||
func latestTenantRevision(st store.Backend, tenantID string) *store.Revision {
|
||||
items, _, _ := st.ListRevisions(tenantID, "", "", 1)
|
||||
if len(items) == 0 {
|
||||
return nil
|
||||
}
|
||||
return items[0]
|
||||
}
|
||||
|
||||
// hashAggregatedMaterialization hashes the full tenant-wide prefix set used for BIRD (all enabled modules).
|
||||
func hashAggregatedMaterialization(tenantID string, rows []store.PrefixRow) string {
|
||||
type line struct{ p, c, s string }
|
||||
|
||||
@@ -25,27 +25,50 @@ func TestRefreshModule_AggregatesAllEnabledModules(t *testing.T) {
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
if _, err := RefreshModule(ctx, m, http.DefaultClient, tenant, modIP); err != nil {
|
||||
before, _, _ := m.ListRevisions(tenant, "", "", 200)
|
||||
beforeN := len(before)
|
||||
|
||||
rev1, err := RefreshModule(ctx, m, http.DefaultClient, tenant, modIP)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
revs, _, _ := m.ListRevisions(tenant, modIP, "", 1)
|
||||
if len(revs) == 0 {
|
||||
t.Fatal("no revision")
|
||||
after1, _, _ := m.ListRevisions(tenant, "", "", 200)
|
||||
if len(after1) != beforeN+1 {
|
||||
t.Fatalf("first refresh: want one new tenant revision, got %d -> %d", beforeN, len(after1))
|
||||
}
|
||||
px, _, _ := m.ListRevisionPrefixes(tenant, revs[0].ID, "", 1000)
|
||||
px, _, _ := m.ListRevisionPrefixes(tenant, rev1, "", 1000)
|
||||
if len(px) != 2 {
|
||||
t.Fatalf("first refresh: want 2 aggregated prefixes, got %d: %+v", len(px), px)
|
||||
}
|
||||
|
||||
if _, err := RefreshModule(ctx, m, http.DefaultClient, tenant, mod2.ID); err != nil {
|
||||
rev2, err := RefreshModule(ctx, m, http.DefaultClient, tenant, mod2.ID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
revs2, _, _ := m.ListRevisions(tenant, mod2.ID, "", 1)
|
||||
if len(revs2) == 0 {
|
||||
t.Fatal("no revision for mod2")
|
||||
if rev2 != rev1 {
|
||||
t.Fatalf("second refresh: same materialization, want same revision id, got %s vs %s", rev2, rev1)
|
||||
}
|
||||
px2, _, _ := m.ListRevisionPrefixes(tenant, revs2[0].ID, "", 1000)
|
||||
if len(px2) != 2 {
|
||||
t.Fatalf("second refresh: want 2 aggregated prefixes, got %d: %+v", len(px2), px2)
|
||||
after2, _, _ := m.ListRevisions(tenant, "", "", 200)
|
||||
if len(after2) != len(after1) {
|
||||
t.Fatalf("second refresh: want no extra revision, had %d now %d", len(after1), len(after2))
|
||||
}
|
||||
|
||||
if _, err := m.CreateIPRangeEntry(tenant, mod2.ID, &store.IPRangeEntry{Prefix: "10.0.1.0/24"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
rev3, err := RefreshModule(ctx, m, http.DefaultClient, tenant, mod2.ID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if rev3 == rev1 {
|
||||
t.Fatal("after prefix change, expected a new revision")
|
||||
}
|
||||
after3, _, _ := m.ListRevisions(tenant, "", "", 200)
|
||||
if len(after3) != len(after2)+1 {
|
||||
t.Fatalf("third refresh: want one new revision, had %d now %d", len(after2), len(after3))
|
||||
}
|
||||
px3, _, _ := m.ListRevisionPrefixes(tenant, rev3, "", 1000)
|
||||
if len(px3) != 3 {
|
||||
t.Fatalf("third refresh: want 3 prefixes, got %d", len(px3))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -954,7 +954,7 @@ func (p *Postgres) DeleteDohProfile(tenantID, id string) error {
|
||||
|
||||
func (p *Postgres) ListCommunities(tenantID string) ([]*store.Community, error) {
|
||||
ctx := context.Background()
|
||||
rows, err := p.pool.Query(ctx, `SELECT id::text, name, kind, value_json::text FROM bgp_community WHERE tenant_id=$1`, tenantID)
|
||||
rows, err := p.pool.Query(ctx, `SELECT id::text, community, title, value_json::text FROM bgp_community WHERE tenant_id=$1 ORDER BY COALESCE(NULLIF(trim(title), ''), community)`, tenantID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -963,7 +963,7 @@ func (p *Postgres) ListCommunities(tenantID string) ([]*store.Community, error)
|
||||
for rows.Next() {
|
||||
var c store.Community
|
||||
c.TenantID = tenantID
|
||||
if err := rows.Scan(&c.ID, &c.Name, &c.Kind, &c.ValueJSON); err != nil {
|
||||
if err := rows.Scan(&c.ID, &c.Community, &c.Title, &c.ValueJSON); err != nil {
|
||||
continue
|
||||
}
|
||||
out = append(out, &c)
|
||||
@@ -975,8 +975,8 @@ func (p *Postgres) GetCommunity(tenantID, id string) (*store.Community, error) {
|
||||
ctx := context.Background()
|
||||
var c store.Community
|
||||
c.TenantID = tenantID
|
||||
err := p.pool.QueryRow(ctx, `SELECT id::text, name, kind, value_json::text FROM bgp_community WHERE id=$1 AND tenant_id=$2`, id, tenantID).Scan(
|
||||
&c.ID, &c.Name, &c.Kind, &c.ValueJSON)
|
||||
err := p.pool.QueryRow(ctx, `SELECT id::text, community, title, value_json::text FROM bgp_community WHERE id=$1 AND tenant_id=$2`, id, tenantID).Scan(
|
||||
&c.ID, &c.Community, &c.Title, &c.ValueJSON)
|
||||
if err != nil {
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return nil, store.ErrNotFound
|
||||
@@ -990,14 +990,19 @@ func (p *Postgres) CreateCommunity(tenantID string, in *store.Community) (*store
|
||||
if in == nil {
|
||||
return nil, store.ErrInvalidInput
|
||||
}
|
||||
if strings.TrimSpace(in.Community) == "" {
|
||||
return nil, store.ErrInvalidInput
|
||||
}
|
||||
ctx := context.Background()
|
||||
id := uuid.NewString()
|
||||
vj := in.ValueJSON
|
||||
if strings.TrimSpace(vj) == "" {
|
||||
vj = "{}"
|
||||
}
|
||||
_, err := p.pool.Exec(ctx, `INSERT INTO bgp_community (id, tenant_id, name, kind, value_json) VALUES ($1,$2,$3,$4,$5::jsonb)`,
|
||||
id, tenantID, in.Name, in.Kind, vj)
|
||||
comm := strings.TrimSpace(in.Community)
|
||||
title := strings.TrimSpace(in.Title)
|
||||
_, err := p.pool.Exec(ctx, `INSERT INTO bgp_community (id, tenant_id, community, title, value_json) VALUES ($1,$2,$3,$4,$5::jsonb)`,
|
||||
id, tenantID, comm, title, vj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -1009,18 +1014,21 @@ func (p *Postgres) UpdateCommunity(tenantID, id string, patch *store.CommunityPa
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if patch.Name != nil {
|
||||
cur.Name = *patch.Name
|
||||
if patch.Community != nil {
|
||||
cur.Community = strings.TrimSpace(*patch.Community)
|
||||
}
|
||||
if patch.Kind != nil {
|
||||
cur.Kind = *patch.Kind
|
||||
if patch.Title != nil {
|
||||
cur.Title = strings.TrimSpace(*patch.Title)
|
||||
}
|
||||
if patch.ValueJSON != nil {
|
||||
cur.ValueJSON = *patch.ValueJSON
|
||||
}
|
||||
if strings.TrimSpace(cur.Community) == "" {
|
||||
return nil, store.ErrInvalidInput
|
||||
}
|
||||
ctx := context.Background()
|
||||
_, err = p.pool.Exec(ctx, `UPDATE bgp_community SET name=$3, kind=$4, value_json=$5::jsonb, updated_at=now() WHERE id=$1 AND tenant_id=$2`,
|
||||
id, tenantID, cur.Name, cur.Kind, cur.ValueJSON)
|
||||
_, err = p.pool.Exec(ctx, `UPDATE bgp_community SET community=$3, title=$4, value_json=$5::jsonb, updated_at=now() WHERE id=$1 AND tenant_id=$2`,
|
||||
id, tenantID, cur.Community, cur.Title, cur.ValueJSON)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ protocol direct {
|
||||
if _, err := tx.Exec(ctx, `INSERT INTO tenant (id, name, slug) VALUES ($1,'Demo','demo')`, tid); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := tx.Exec(ctx, `INSERT INTO bgp_community (id, tenant_id, name, kind, value_json) VALUES ($1,$2,'demo-comm','large','{}')`, cid, tid); err != nil {
|
||||
if _, err := tx.Exec(ctx, `INSERT INTO bgp_community (id, tenant_id, community, title, value_json) VALUES ($1,$2,'demo-comm','Demo','{}')`, cid, tid); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := tx.Exec(ctx, `
|
||||
|
||||
@@ -173,14 +173,14 @@ type DohProfilePatch struct {
|
||||
type Community struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
TenantID string `json:"tenant_id,omitempty"`
|
||||
Name string `json:"name"`
|
||||
Kind string `json:"kind"`
|
||||
Community string `json:"community"`
|
||||
Title string `json:"title"`
|
||||
ValueJSON string `json:"value_json"`
|
||||
}
|
||||
|
||||
type CommunityPatch struct {
|
||||
Name *string `json:"name,omitempty"`
|
||||
Kind *string `json:"kind,omitempty"`
|
||||
Community *string `json:"community,omitempty"`
|
||||
Title *string `json:"title,omitempty"`
|
||||
ValueJSON *string `json:"value_json,omitempty"`
|
||||
}
|
||||
|
||||
|
||||
@@ -235,7 +235,7 @@ protocol direct {
|
||||
|
||||
// Demo materialized prefixes for /revisions/{id}/prefixes
|
||||
cid := uuid.NewString()
|
||||
m.communities[cid] = &Community{ID: cid, TenantID: tid, Name: "demo-comm", Kind: "large", ValueJSON: "{}"}
|
||||
m.communities[cid] = &Community{ID: cid, TenantID: tid, Community: "demo-comm", Title: "Demo", ValueJSON: "{}"}
|
||||
m.revPrefixes[rid] = []PrefixRow{
|
||||
{Prefix: "203.0.113.0/24", CommunityID: &cid, Source: "demo"},
|
||||
{Prefix: "2001:db8::/32", CommunityID: &cid, Source: "demo"},
|
||||
|
||||
@@ -569,7 +569,7 @@ func (m *Memory) GetCommunity(tenantID, id string) (*Community, error) {
|
||||
}
|
||||
|
||||
func (m *Memory) CreateCommunity(tenantID string, in *Community) (*Community, error) {
|
||||
if in == nil || strings.TrimSpace(in.Kind) == "" {
|
||||
if in == nil || strings.TrimSpace(in.Community) == "" {
|
||||
return nil, ErrInvalidInput
|
||||
}
|
||||
m.mu.Lock()
|
||||
@@ -582,7 +582,7 @@ func (m *Memory) CreateCommunity(tenantID string, in *Community) (*Community, er
|
||||
if strings.TrimSpace(vj) == "" {
|
||||
vj = "{}"
|
||||
}
|
||||
c := &Community{ID: id, TenantID: tenantID, Name: in.Name, Kind: strings.TrimSpace(in.Kind), ValueJSON: vj}
|
||||
c := &Community{ID: id, TenantID: tenantID, Community: strings.TrimSpace(in.Community), Title: strings.TrimSpace(in.Title), ValueJSON: vj}
|
||||
m.communities[id] = c
|
||||
return c, nil
|
||||
}
|
||||
@@ -597,11 +597,11 @@ func (m *Memory) UpdateCommunity(tenantID, id string, patch *CommunityPatch) (*C
|
||||
if !ok || c.TenantID != tenantID {
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
if patch.Name != nil {
|
||||
c.Name = *patch.Name
|
||||
if patch.Community != nil {
|
||||
c.Community = strings.TrimSpace(*patch.Community)
|
||||
}
|
||||
if patch.Kind != nil {
|
||||
c.Kind = strings.TrimSpace(*patch.Kind)
|
||||
if patch.Title != nil {
|
||||
c.Title = strings.TrimSpace(*patch.Title)
|
||||
}
|
||||
if patch.ValueJSON != nil {
|
||||
c.ValueJSON = *patch.ValueJSON
|
||||
|
||||
Reference in New Issue
Block a user