feat(backends): Pool-Modell — Backend = Pool, N Server pro Backend
Migration 0016: backend_servers (id, backend_id, name, address, port, weight, backup, active) + backends.lb_algorithm. Daten-Migration kopiert bestehende backends.address/port als ersten Server, dann DROP COLUMN. HAProxy-Renderer: rendert pro Backend einen Block mit `balance <algo>` + N `server`-Zeilen (weight, backup-Flag, optional check inter 5s). LB-Algorithmen: roundrobin / leastconn / source. REST: /backends/:id/servers (GET/POST), /backend-servers/:id (PUT/DELETE). Re-rendert HAProxy nach jeder Server-Mutation. UI: address/port aus Backend-Form raus, lb_algorithm-Select rein. Server verwaltet ein expandable Sub-Panel pro Backend-Row (Tabelle + Add/Edit/ Delete-Modal). Domain-Attachment-Multi-Select bleibt. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
144
internal/services/backendservers/backendservers.go
Normal file
144
internal/services/backendservers/backendservers.go
Normal file
@@ -0,0 +1,144 @@
|
||||
// Package backendservers implements CRUD against the backend_servers
|
||||
// table — die konkreten Upstream-Server pro Backend-Pool. Schema in
|
||||
// migration 0016_backend_servers.sql.
|
||||
package backendservers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
|
||||
"git.netcell-it.de/projekte/edgeguard-native/internal/models"
|
||||
)
|
||||
|
||||
var ErrNotFound = errors.New("backend server not found")
|
||||
|
||||
type Repo struct {
|
||||
Pool *pgxpool.Pool
|
||||
}
|
||||
|
||||
func New(pool *pgxpool.Pool) *Repo { return &Repo{Pool: pool} }
|
||||
|
||||
const baseSelect = `
|
||||
SELECT id, backend_id, name, address, port, weight, backup, active,
|
||||
created_at, updated_at
|
||||
FROM backend_servers
|
||||
`
|
||||
|
||||
// ListByBackend liefert alle Server eines Pools. Sortierung: backup
|
||||
// am Ende (HAProxy bedient sie nur wenn primaries down sind, in der
|
||||
// UI sollen sie auch optisch unten stehen), dann nach name.
|
||||
func (r *Repo) ListByBackend(ctx context.Context, backendID int64) ([]models.BackendServer, error) {
|
||||
rows, err := r.Pool.Query(ctx,
|
||||
baseSelect+" WHERE backend_id = $1 ORDER BY backup ASC, name ASC", backendID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
out := make([]models.BackendServer, 0, 4)
|
||||
for rows.Next() {
|
||||
s, err := scan(rows)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out = append(out, *s)
|
||||
}
|
||||
return out, rows.Err()
|
||||
}
|
||||
|
||||
// ListAll gibt sämtliche Server zurück — wird vom HAProxy-Renderer
|
||||
// genutzt, damit der View in einem einzigen Query alle Pools füllt.
|
||||
func (r *Repo) ListAll(ctx context.Context) ([]models.BackendServer, error) {
|
||||
rows, err := r.Pool.Query(ctx,
|
||||
baseSelect+" ORDER BY backend_id ASC, backup ASC, name ASC")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
out := make([]models.BackendServer, 0, 16)
|
||||
for rows.Next() {
|
||||
s, err := scan(rows)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out = append(out, *s)
|
||||
}
|
||||
return out, rows.Err()
|
||||
}
|
||||
|
||||
func (r *Repo) Get(ctx context.Context, id int64) (*models.BackendServer, error) {
|
||||
row := r.Pool.QueryRow(ctx, baseSelect+" WHERE id = $1", id)
|
||||
s, err := scan(row)
|
||||
if err != nil {
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (r *Repo) Create(ctx context.Context, s models.BackendServer) (*models.BackendServer, error) {
|
||||
if s.Weight == 0 {
|
||||
s.Weight = 100
|
||||
}
|
||||
row := r.Pool.QueryRow(ctx, `
|
||||
INSERT INTO backend_servers (backend_id, name, address, port, weight, backup, active)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
||||
RETURNING id, backend_id, name, address, port, weight, backup, active,
|
||||
created_at, updated_at`,
|
||||
s.BackendID, s.Name, s.Address, s.Port, s.Weight, s.Backup, s.Active)
|
||||
return scan(row)
|
||||
}
|
||||
|
||||
func (r *Repo) Update(ctx context.Context, id int64, s models.BackendServer) (*models.BackendServer, error) {
|
||||
if s.Weight == 0 {
|
||||
s.Weight = 100
|
||||
}
|
||||
row := r.Pool.QueryRow(ctx, `
|
||||
UPDATE backend_servers SET
|
||||
name = $1,
|
||||
address = $2,
|
||||
port = $3,
|
||||
weight = $4,
|
||||
backup = $5,
|
||||
active = $6,
|
||||
updated_at = NOW()
|
||||
WHERE id = $7
|
||||
RETURNING id, backend_id, name, address, port, weight, backup, active,
|
||||
created_at, updated_at`,
|
||||
s.Name, s.Address, s.Port, s.Weight, s.Backup, s.Active, id)
|
||||
out, err := scan(row)
|
||||
if err != nil {
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (r *Repo) Delete(ctx context.Context, id int64) error {
|
||||
tag, err := r.Pool.Exec(ctx, `DELETE FROM backend_servers WHERE id = $1`, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if tag.RowsAffected() == 0 {
|
||||
return ErrNotFound
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func scan(row interface{ Scan(...any) error }) (*models.BackendServer, error) {
|
||||
var s models.BackendServer
|
||||
if err := row.Scan(
|
||||
&s.ID, &s.BackendID, &s.Name, &s.Address, &s.Port,
|
||||
&s.Weight, &s.Backup, &s.Active,
|
||||
&s.CreatedAt, &s.UpdatedAt,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &s, nil
|
||||
}
|
||||
Reference in New Issue
Block a user