docs: update architecture and API documentation to clarify job processing, in-memory registry usage, and future broker integration plans. Enhance OpenAPI specifications for enrollment responses and refine quickstart instructions for Docker setups.
CI / changes (push) Successful in 4s
CI / openapi (push) Successful in 24s
CI / go (push) Successful in 22s
CI / bird2 (push) Successful in 13s
CI / docker-images (deploy/docker/bird2/Dockerfile, evobgp-bird2) (push) Successful in 38s
CI / docker-images (deploy/docker/evobgp-agent/Dockerfile, evobgp-agent) (push) Successful in 1m8s
CI / docker-images (deploy/docker/evobgp-web/Dockerfile, evobgp-web) (push) Successful in 49s
CI / docker-images (evobgp-all, 1, deploy/docker/gobinary/Dockerfile, evobgp-all) (push) Successful in 1m30s
CI / docker-images (evobgp-api, 1, deploy/docker/gobinary/Dockerfile, evobgp-api) (push) Successful in 1m31s
CI / docker-images (evobgp-ingest, 0, deploy/docker/gobinary/Dockerfile, evobgp-ingest) (push) Has been cancelled
CI / docker-images (evobgp-deploy, 0, deploy/docker/gobinary/Dockerfile, evobgp-deploy) (push) Has been cancelled
CI / docker-images (evobgp-node, 0, deploy/docker/gobinary/Dockerfile, evobgp-node) (push) Has been cancelled
CI / docker-images (evobgp-render, 0, deploy/docker/gobinary/Dockerfile, evobgp-render) (push) Has been cancelled
CI / docker-images (evobgp-scheduler, 0, deploy/docker/gobinary/Dockerfile, evobgp-scheduler) (push) Has been cancelled
CI / changes (push) Successful in 4s
CI / openapi (push) Successful in 24s
CI / go (push) Successful in 22s
CI / bird2 (push) Successful in 13s
CI / docker-images (deploy/docker/bird2/Dockerfile, evobgp-bird2) (push) Successful in 38s
CI / docker-images (deploy/docker/evobgp-agent/Dockerfile, evobgp-agent) (push) Successful in 1m8s
CI / docker-images (deploy/docker/evobgp-web/Dockerfile, evobgp-web) (push) Successful in 49s
CI / docker-images (evobgp-all, 1, deploy/docker/gobinary/Dockerfile, evobgp-all) (push) Successful in 1m30s
CI / docker-images (evobgp-api, 1, deploy/docker/gobinary/Dockerfile, evobgp-api) (push) Successful in 1m31s
CI / docker-images (evobgp-ingest, 0, deploy/docker/gobinary/Dockerfile, evobgp-ingest) (push) Has been cancelled
CI / docker-images (evobgp-deploy, 0, deploy/docker/gobinary/Dockerfile, evobgp-deploy) (push) Has been cancelled
CI / docker-images (evobgp-node, 0, deploy/docker/gobinary/Dockerfile, evobgp-node) (push) Has been cancelled
CI / docker-images (evobgp-render, 0, deploy/docker/gobinary/Dockerfile, evobgp-render) (push) Has been cancelled
CI / docker-images (evobgp-scheduler, 0, deploy/docker/gobinary/Dockerfile, evobgp-scheduler) (push) Has been cancelled
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
// Package broker stubs reference-profile message broker wiring (NATS JetStream / Redis; plan §2).
|
||||
// Package broker holds optional hooks for a future shared message bus (NATS JetStream / Redis).
|
||||
// См. docs/architecture.md: в текущей сборке очередь задач in-process в API; EVOBGP_BROKER_URL зарезервирован.
|
||||
package broker
|
||||
|
||||
import (
|
||||
@@ -7,12 +8,12 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// LogConnect logs a one-shot connection attempt when EVOBGP_BROKER_URL is set (full consumer/producer TBD).
|
||||
// LogConnect logs once when EVOBGP_BROKER_URL is set. Multi-process job delivery via a broker is not enabled in this build.
|
||||
func LogConnect(ctx context.Context, brokerURL string) {
|
||||
u := strings.TrimSpace(brokerURL)
|
||||
if u == "" {
|
||||
return
|
||||
}
|
||||
log.Printf("broker: EVOBGP_BROKER_URL=%q (stub: no JetStream/Streams consumer in this binary yet)", u)
|
||||
log.Printf("broker: EVOBGP_BROKER_URL=%q set; control plane still uses in-process jobs.Registry (no JetStream/Redis consumer in this binary)", u)
|
||||
_ = ctx
|
||||
}
|
||||
|
||||
+1
-16
@@ -25,8 +25,7 @@ func Run(ctx context.Context, deps *Deps) {
|
||||
log.Printf("evobgp-deploy: EVOBGP_BIRD_ACTIVE_DIR=%q (apply via API jobs when API has same env)", d)
|
||||
}
|
||||
if deps == nil || deps.Store == nil {
|
||||
runStub(ctx)
|
||||
return
|
||||
log.Fatalf("evobgp-deploy: missing store (pass deploy.Deps from BootstrapWorkers or evobgp-all)")
|
||||
}
|
||||
t := time.NewTicker(90 * time.Second)
|
||||
defer t.Stop()
|
||||
@@ -65,17 +64,3 @@ func logDrift(ctx context.Context, st store.Backend) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func runStub(ctx context.Context) {
|
||||
t := time.NewTicker(60 * time.Second)
|
||||
defer t.Stop()
|
||||
log.Printf("evobgp-deploy: idle stub (no store in Deps)")
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
log.Printf("evobgp-deploy: stopped")
|
||||
return
|
||||
case <-t.C:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ import (
|
||||
"crypto/ed25519"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"sort"
|
||||
@@ -672,14 +674,72 @@ func (s *Server) handleNodeEnroll(w http.ResponseWriter, r *http.Request) {
|
||||
if !s.requireNode(w, a) {
|
||||
return
|
||||
}
|
||||
var req map[string]any
|
||||
_ = json.NewDecoder(r.Body).Decode(&req)
|
||||
var body struct {
|
||||
SpeakerID string `json:"speaker_id"`
|
||||
PublicKey string `json:"public_key"`
|
||||
}
|
||||
dec := json.NewDecoder(r.Body)
|
||||
if err := dec.Decode(&body); err != nil && !errors.Is(err, io.EOF) {
|
||||
writeProblem(w, http.StatusBadRequest, "Bad Request", "invalid JSON body")
|
||||
return
|
||||
}
|
||||
sid := strings.TrimSpace(body.SpeakerID)
|
||||
if sid == "" {
|
||||
writeProblem(w, http.StatusUnprocessableEntity, "Unprocessable Entity", "speaker_id is required")
|
||||
return
|
||||
}
|
||||
sp, err := s.store.GetSpeaker(a.TenantID, sid)
|
||||
if err != nil {
|
||||
writeProblem(w, http.StatusNotFound, "Not Found", "speaker not found")
|
||||
return
|
||||
}
|
||||
updates := map[string]any{
|
||||
"node_enrolled_at": time.Now().UTC().Format(time.RFC3339Nano),
|
||||
}
|
||||
if pk := strings.TrimSpace(body.PublicKey); pk != "" {
|
||||
updates["node_public_key"] = pk
|
||||
}
|
||||
meta, err := mergeSpeakerMetaJSON(sp.MetaJSON, updates)
|
||||
if err != nil {
|
||||
writeProblem(w, http.StatusUnprocessableEntity, "Unprocessable Entity", "speaker meta_json must be a JSON object (or empty)")
|
||||
return
|
||||
}
|
||||
patch := &store.SpeakerPatch{MetaJSON: &meta}
|
||||
if _, err := s.store.UpdateSpeaker(a.TenantID, sid, patch); err != nil {
|
||||
writeProblem(w, http.StatusInternalServerError, "Internal Error", err.Error())
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, map[string]any{
|
||||
"status": "accepted",
|
||||
"message": "enrollment stub; operator approval required in production",
|
||||
"status": "enrolled",
|
||||
"speaker_id": sp.ID,
|
||||
"tenant_id": a.TenantID,
|
||||
})
|
||||
}
|
||||
|
||||
func mergeSpeakerMetaJSON(existing string, updates map[string]any) (string, error) {
|
||||
existing = strings.TrimSpace(existing)
|
||||
var m map[string]any
|
||||
if existing != "" {
|
||||
if err := json.Unmarshal([]byte(existing), &m); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if m == nil {
|
||||
return "", errors.New("meta must be a JSON object")
|
||||
}
|
||||
}
|
||||
if m == nil {
|
||||
m = make(map[string]any)
|
||||
}
|
||||
for k, v := range updates {
|
||||
m[k] = v
|
||||
}
|
||||
b, err := json.Marshal(m)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(b), nil
|
||||
}
|
||||
|
||||
func sortedFragmentKeys(m map[string]string) []string {
|
||||
keys := make([]string, 0, len(m))
|
||||
for k := range m {
|
||||
|
||||
+1
-16
@@ -22,8 +22,7 @@ func Run(ctx context.Context, deps *Deps) {
|
||||
cfg := config.Load()
|
||||
broker.LogConnect(ctx, cfg.BrokerURL)
|
||||
if deps == nil || deps.Store == nil {
|
||||
runStub(ctx)
|
||||
return
|
||||
log.Fatalf("evobgp-ingest: missing store (pass ingest.Deps from BootstrapWorkers or evobgp-all)")
|
||||
}
|
||||
hc := &http.Client{Timeout: 45 * time.Second}
|
||||
t := time.NewTicker(60 * time.Second)
|
||||
@@ -41,17 +40,3 @@ func Run(ctx context.Context, deps *Deps) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func runStub(ctx context.Context) {
|
||||
t := time.NewTicker(60 * time.Second)
|
||||
defer t.Stop()
|
||||
log.Printf("evobgp-ingest: idle stub (no store in Deps)")
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
log.Printf("evobgp-ingest: stopped")
|
||||
return
|
||||
case <-t.C:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ const (
|
||||
StatusCancelled = "cancelled"
|
||||
)
|
||||
|
||||
// Job is the API-facing job model (maps to job_audit).
|
||||
// Job is the API-facing job model (поля согласованы со схемой job_audit в миграциях; персистенция в БД пока не подключена).
|
||||
type Job struct {
|
||||
ID string
|
||||
TenantID string
|
||||
@@ -148,7 +148,9 @@ func (j *Job) Snapshot() map[string]any {
|
||||
return m
|
||||
}
|
||||
|
||||
// Registry tracks jobs in memory (microVPS-style single process; swap for PG + SKIP LOCKED later).
|
||||
// Registry — in-memory очередь и индекс по idempotency в процессе, где поднят HTTP API (evobgp-api и evobgp-all).
|
||||
// Отдельные воркеры в reference-профиле не разделяют память с API: scheduler дергает refresh по HTTP; см. docs/architecture.md.
|
||||
// Запись задач в PostgreSQL job_audit + SKIP LOCKED / внешний брокер — планируемое расширение (архитектурный план §2, §7.10).
|
||||
type Registry struct {
|
||||
mu sync.RWMutex
|
||||
byID map[string]*Job
|
||||
|
||||
+1
-16
@@ -22,8 +22,7 @@ func Run(ctx context.Context, deps *Deps) {
|
||||
cfg := config.Load()
|
||||
broker.LogConnect(ctx, cfg.BrokerURL)
|
||||
if deps == nil || deps.Store == nil {
|
||||
runStub(ctx)
|
||||
return
|
||||
log.Fatalf("evobgp-render: missing store (pass render.Deps from BootstrapWorkers or evobgp-all)")
|
||||
}
|
||||
t := time.NewTicker(45 * time.Second)
|
||||
defer t.Stop()
|
||||
@@ -45,17 +44,3 @@ func Run(ctx context.Context, deps *Deps) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func runStub(ctx context.Context) {
|
||||
t := time.NewTicker(60 * time.Second)
|
||||
defer t.Stop()
|
||||
log.Printf("evobgp-render: idle stub (no store in Deps)")
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
log.Printf("evobgp-render: stopped")
|
||||
return
|
||||
case <-t.C:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Package repository will host SQL-backed data access (plan §1: tenant, module, peer, revision, jobs).
|
||||
// Package repository — доступ к PostgreSQL (план §1: tenant, module, peer, revision, сущности модулей и т.д.).
|
||||
//
|
||||
// The in-memory implementation used by the current API lives in internal/store.
|
||||
// Migrations: see migrations/ at repository root.
|
||||
// In-memory реализация для тестов и режима без БД — в internal/store.
|
||||
// Миграции: каталог migrations/ в корне репозитория.
|
||||
|
||||
package repository
|
||||
|
||||
@@ -1029,7 +1029,7 @@ func (p *Postgres) DeleteCommunity(tenantID, id string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// CDN / AS / domain / IP stubs: implement in postgres_sub.go to keep file size manageable.
|
||||
// CDN / AS / domain / IP / settings: см. postgres_entities.go.
|
||||
|
||||
func strPtrUUID(s *string) *string {
|
||||
if s == nil || strings.TrimSpace(*s) == "" {
|
||||
|
||||
@@ -556,5 +556,3 @@ func (p *Postgres) PatchGlobalSettings(tenantID string, patch map[string]any) er
|
||||
}
|
||||
|
||||
var _ store.Backend = (*Postgres)(nil)
|
||||
|
||||
var _ store.Backend = (*Postgres)(nil)
|
||||
|
||||
@@ -26,17 +26,15 @@ type Deps struct {
|
||||
HTTP *http.Client
|
||||
}
|
||||
|
||||
// Run blocks until ctx is cancelled. When deps is nil or incomplete, falls back to connect-only stub logging.
|
||||
// Run blocks until ctx is cancelled. Misconfigured deps terminate the process (no idle fallback).
|
||||
func Run(ctx context.Context, deps *Deps) {
|
||||
cfg := config.Load()
|
||||
broker.LogConnect(ctx, cfg.BrokerURL)
|
||||
if deps == nil || deps.Store == nil {
|
||||
runStub(ctx)
|
||||
return
|
||||
log.Fatalf("evobgp-scheduler: missing store (pass scheduler.Deps with Store from BootstrapWorkers or evobgp-all)")
|
||||
}
|
||||
if deps.Jobs == nil && (strings.TrimSpace(deps.APIBase) == "" || strings.TrimSpace(deps.APIToken) == "") {
|
||||
runStub(ctx)
|
||||
return
|
||||
log.Fatalf("evobgp-scheduler: need either in-process Jobs (evobgp-all) or both EVOBGP_CONTROL_PLANE_URL and EVOBGP_SCHEDULER_BEARER")
|
||||
}
|
||||
t := time.NewTicker(30 * time.Second)
|
||||
defer t.Stop()
|
||||
@@ -125,17 +123,3 @@ func postModuleRefresh(ctx context.Context, deps *Deps, moduleID, idempotencyKey
|
||||
b, _ := io.ReadAll(io.LimitReader(resp.Body, 4096))
|
||||
return fmt.Errorf("%s: %s", resp.Status, strings.TrimSpace(string(b)))
|
||||
}
|
||||
|
||||
func runStub(ctx context.Context) {
|
||||
t := time.NewTicker(60 * time.Second)
|
||||
defer t.Stop()
|
||||
log.Printf("evobgp-scheduler: idle stub (no store/jobs: pass scheduler.Deps from evobgp-all or bootstrap worker)")
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
log.Printf("evobgp-scheduler: stopped")
|
||||
return
|
||||
case <-t.C:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user