Add WatchEvents, enable/disable/weight RPCs, and config check

gRPC / proto
- Rename WatchBackendEvents → WatchEvents; return a stream of Event
  oneof (LogEvent, BackendEvent, FrontendEvent) with optional filter
  flags (log, log_level, backend, frontend)
- Add EnableBackend, DisableBackend, SetFrontendPoolBackendWeight RPCs
- Rename PauseResumeRequest → BackendRequest
- Add CheckConfig RPC returning ok/parse_error/semantic_error

maglevd
- Route slog through a LogBroadcaster (slog.Handler) so WatchEvents
  subscribers can receive structured log records independently of the
  daemon's own --log-level
- Add --reflection flag (default true) to toggle gRPC server reflection
- Add --check flag: validates config file and exits 0/1/2
- SIGHUP: use config.Check before applying reload; log parse vs semantic
  error separately; refuse reload on any error
- Rename default config path /etc/maglev → /etc/vpp-maglev

maglevc
- Add 'watch events [num <n>] [log [level <level>]] [backend] [frontend]'
  command; prints compact protojson, stops on any keypress or Ctrl-C;
  uses cbreak mode (not raw) so output post-processing is preserved
- Add 'set backend <name> enable|disable'
- Add 'set frontend <name> pool <pool> backend <name> weight <0-100>'
- Add 'config check' command

Debian packaging
- Rename service unit to vpp-maglevd.service
- Rename conffiles to /etc/default/vpp-maglev and /etc/vpp-maglev/
- Create maglevd system user/group in postinst; add to vpp group if present
- Add postrm; add adduser to Depends
This commit is contained in:
2026-04-11 16:42:11 +02:00
parent d612086a5f
commit 58391f5463
26 changed files with 1969 additions and 400 deletions

View File

@@ -156,21 +156,58 @@ type rawFrontend struct {
Pools []rawPool `yaml:"pools"`
}
// ---- Load ------------------------------------------------------------------
// ---- Check / Load ----------------------------------------------------------
// CheckResult holds the outcome of a config file validation. Exactly one of
// ParseError and SemanticError is non-empty when the config is invalid; both
// are empty on success.
type CheckResult struct {
ParseError string // YAML could not be read or parsed
SemanticError string // YAML parsed but semantic validation failed
}
// OK reports whether the config is valid.
func (r CheckResult) OK() bool {
return r.ParseError == "" && r.SemanticError == ""
}
// Check reads and validates the config file at path, returning the parsed
// Config (nil on failure) and a CheckResult that distinguishes YAML parse
// errors from semantic validation errors.
func Check(path string) (*Config, CheckResult) {
data, err := os.ReadFile(path)
if err != nil {
return nil, CheckResult{ParseError: fmt.Sprintf("read %q: %v", path, err)}
}
var raw rawConfig
if err := yaml.Unmarshal(data, &raw); err != nil {
return nil, CheckResult{ParseError: fmt.Sprintf("parse yaml: %v", err)}
}
cfg, err := convert(&raw.Maglev)
if err != nil {
return nil, CheckResult{SemanticError: err.Error()}
}
return cfg, CheckResult{}
}
// Load reads and validates the config file at path.
func Load(path string) (*Config, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("read config %q: %w", path, err)
cfg, result := Check(path)
if !result.OK() {
if result.ParseError != "" {
return nil, fmt.Errorf("%s", result.ParseError)
}
return nil, fmt.Errorf("%s", result.SemanticError)
}
return parse(data)
return cfg, nil
}
// parse unmarshals raw YAML bytes and converts them into a validated Config.
// Used by tests; production code goes through Check or Load.
func parse(data []byte) (*Config, error) {
var raw rawConfig
if err := yaml.Unmarshal(data, &raw); err != nil {
return nil, fmt.Errorf("parse yaml: %w", err)
return nil, fmt.Errorf("parse yaml: %v", err)
}
return convert(&raw.Maglev)
}