Refactor CLI: birdc-style help, collapsed nouns, ReloadConfig, bug fixes

maglevc
- Rewrite '?' handler (birdc-style): show full command paths from current
  position to every leaf, right-aligned help column, dynamic slot values
  displayed as an indented block when cursor is at a slot position.
- Collapse show frontends/frontend, backends/backend, healthchecks/healthcheck
  into single plural-noun nodes with an optional <name> slot. Allows
  'sh ba' (list all) and 'sh ba nginx0' (show one) without ambiguity.
- Add 'config reload' command.
- Fix tabwriter ANSI alignment: continuation lines in transition output
  now carry the same label() byte overhead as the header line.
- Fix broken Walk for 'set frontend' command: setFrontendPoolName and
  setWeightValue were fixed-word nodes that couldn't capture user input;
  mark them as slot nodes with dynNone.
- Add tree_test.go covering expandPaths, cycle detection, prefix matching,
  and the full weight-command walk.

gRPC / proto
- Add ReloadConfig RPC: checks config then applies it to the running
  checker, returning ok/parse_error/semantic_error/reload_error.
- Add logging to CheckConfig (config-check-start/config-check-done at
  INFO level).

maglevd
- SIGHUP handler now calls maglevServer.TriggerReload(), sharing the
  same code path as the gRPC ReloadConfig RPC.

docs
- Collapse show command documentation to use [<name>] optional syntax.
- Remove developer-facing 'Command tree and parser' section.
- Document 'config reload'.
This commit is contained in:
2026-04-11 18:20:43 +02:00
parent 58391f5463
commit 3bd30b69f4
11 changed files with 657 additions and 222 deletions

View File

@@ -27,58 +27,55 @@ func buildTree() *Node {
exit := &Node{Word: "exit", Help: "exit the shell", Run: runQuit} exit := &Node{Word: "exit", Help: "exit the shell", Run: runQuit}
// show version // show version
showVersion := &Node{Word: "version", Help: "show build version", Run: runShowVersion} showVersion := &Node{Word: "version", Help: "Show build version", Run: runShowVersion}
// show frontends // show frontends [<name>] — without name: list all, with name: show details
showFrontends := &Node{Word: "frontends", Help: "list all frontends", Run: runShowFrontends}
// show frontend <name>
showFrontendName := &Node{ showFrontendName := &Node{
Word: "<name>", Word: "<name>",
Help: "frontend name", Help: "Show details for a single frontend",
Dynamic: dynFrontends, Dynamic: dynFrontends,
Run: runShowFrontend, Run: runShowFrontend,
} }
showFrontend := &Node{ showFrontends := &Node{
Word: "frontend", Word: "frontends",
Help: "show a single frontend", Help: "List all frontends",
Run: runShowFrontends,
Children: []*Node{showFrontendName}, Children: []*Node{showFrontendName},
} }
// show backends // show backends [<name>] — without name: list all, with name: show details
showBackends := &Node{Word: "backends", Help: "list all backends", Run: runShowBackends}
// show backend <name>
showBackendName := &Node{ showBackendName := &Node{
Word: "<name>", Word: "<name>",
Help: "backend name", Help: "Show details for a single backend",
Dynamic: dynBackends, Dynamic: dynBackends,
Run: runShowBackend, Run: runShowBackend,
} }
showBackend := &Node{ showBackends := &Node{
Word: "backend", Word: "backends",
Help: "show a single backend", Help: "List all backends",
Run: runShowBackends,
Children: []*Node{showBackendName}, Children: []*Node{showBackendName},
} }
// show healthchecks // show healthchecks [<name>] — without name: list all, with name: show details
showHealthChecks := &Node{Word: "healthchecks", Help: "list all health checks", Run: runShowHealthChecks}
// show healthcheck <name>
showHealthCheckName := &Node{ showHealthCheckName := &Node{
Word: "<name>", Word: "<name>",
Help: "health check name", Help: "Show details for a single health check",
Dynamic: dynHealthChecks, Dynamic: dynHealthChecks,
Run: runShowHealthCheck, Run: runShowHealthCheck,
} }
showHealthCheck := &Node{ showHealthChecks := &Node{
Word: "healthcheck", Word: "healthchecks",
Help: "show a single health check", Help: "List all health checks",
Run: runShowHealthChecks,
Children: []*Node{showHealthCheckName}, Children: []*Node{showHealthCheckName},
} }
show.Children = []*Node{ show.Children = []*Node{
showVersion, showVersion,
showFrontends, showFrontend, showFrontends,
showBackends, showBackend, showBackends,
showHealthChecks, showHealthCheck, showHealthChecks,
} }
// set backend <name> pause|resume|disabled|enabled // set backend <name> pause|resume|disabled|enabled
@@ -100,7 +97,8 @@ func buildTree() *Node {
// set frontend <name> pool <pool> backend <name> weight <0-100> // set frontend <name> pool <pool> backend <name> weight <0-100>
setWeightValue := &Node{ setWeightValue := &Node{
Word: "<weight>", Word: "<weight>",
Help: "weight 0-100", Help: "Set weight of a backend in a pool (0-100)",
Dynamic: dynNone, // accepts any integer; no tab-completion candidates
Run: runSetFrontendPoolBackendWeight, Run: runSetFrontendPoolBackendWeight,
} }
setFrontendPoolBackendWeight := &Node{Word: "weight", Help: "set backend weight in pool", Children: []*Node{setWeightValue}} setFrontendPoolBackendWeight := &Node{Word: "weight", Help: "set backend weight in pool", Children: []*Node{setWeightValue}}
@@ -114,6 +112,7 @@ func buildTree() *Node {
setFrontendPoolName := &Node{ setFrontendPoolName := &Node{
Word: "<pool>", Word: "<pool>",
Help: "pool name", Help: "pool name",
Dynamic: dynNone, // pool names aren't listed via gRPC; accepts any input
Children: []*Node{setFrontendPoolBackend}, Children: []*Node{setFrontendPoolBackend},
} }
setFrontendPool := &Node{Word: "pool", Help: "select a pool", Children: []*Node{setFrontendPoolName}} setFrontendPool := &Node{Word: "pool", Help: "select a pool", Children: []*Node{setFrontendPoolName}}
@@ -139,7 +138,7 @@ func buildTree() *Node {
var watchEventsOptSlot *Node var watchEventsOptSlot *Node
watchEventsOptSlot = &Node{ watchEventsOptSlot = &Node{
Word: "<opt>", Word: "<opt>",
Help: "watch option", Help: "Stream events with options",
Dynamic: dynWatchEventOpts, Dynamic: dynWatchEventOpts,
Run: runWatchEvents, Run: runWatchEvents,
} }
@@ -157,12 +156,13 @@ func buildTree() *Node {
Children: []*Node{watchEvents}, Children: []*Node{watchEvents},
} }
// config check // config check / reload
configCheck := &Node{Word: "check", Help: "check configuration file", Run: runConfigCheck} configCheck := &Node{Word: "check", Help: "Check configuration file", Run: runConfigCheck}
configReload := &Node{Word: "reload", Help: "Check and reload configuration", Run: runConfigReload}
configNode := &Node{ configNode := &Node{
Word: "config", Word: "config",
Help: "configuration commands", Help: "configuration commands",
Children: []*Node{configCheck}, Children: []*Node{configCheck, configReload},
} }
root.Children = []*Node{show, set, watch, configNode, quit, exit} root.Children = []*Node{show, set, watch, configNode, quit, exit}
@@ -195,6 +195,10 @@ func dynHealthChecks(ctx context.Context, client grpcapi.MaglevClient) []string
return resp.Names return resp.Names
} }
// dynNone marks a slot node that accepts any input but provides no
// tab-completion candidates (e.g. a pool name or numeric weight value).
func dynNone(_ context.Context, _ grpcapi.MaglevClient) []string { return nil }
// ---- run functions --------------------------------------------------------- // ---- run functions ---------------------------------------------------------
func runShowVersion(_ context.Context, _ grpcapi.MaglevClient, _ []string) error { func runShowVersion(_ context.Context, _ grpcapi.MaglevClient, _ []string) error {
@@ -315,9 +319,14 @@ func runShowBackend(ctx context.Context, client grpcapi.MaglevClient, args []str
fmt.Fprintf(w, "%s\t%s\n", label("healthcheck"), info.Healthcheck) fmt.Fprintf(w, "%s\t%s\n", label("healthcheck"), info.Healthcheck)
for i, t := range info.Transitions { for i, t := range info.Transitions {
ts := time.Unix(0, t.AtUnixNs) ts := time.Unix(0, t.AtUnixNs)
lbl := "" var lbl string
if i == 0 { if i == 0 {
lbl = label("transitions") lbl = label("transitions")
} else {
// Pad to same visible width as "transitions" and wrap through
// label() so tabwriter sees the same byte count (ANSI overhead
// is identical on every row, keeping columns aligned).
lbl = label(" ")
} }
fmt.Fprintf(w, "%s\t%s → %s\t%s\t%s\n", fmt.Fprintf(w, "%s\t%s → %s\t%s\t%s\n",
lbl, lbl,
@@ -501,6 +510,26 @@ func runConfigCheck(ctx context.Context, client grpcapi.MaglevClient, _ []string
return fmt.Errorf("semantic error: %s", resp.SemanticError) return fmt.Errorf("semantic error: %s", resp.SemanticError)
} }
func runConfigReload(ctx context.Context, client grpcapi.MaglevClient, _ []string) error {
ctx, cancel := context.WithTimeout(ctx, callTimeout)
defer cancel()
resp, err := client.ReloadConfig(ctx, &grpcapi.ReloadConfigRequest{})
if err != nil {
return err
}
if resp.Ok {
fmt.Println("config reloaded")
return nil
}
if resp.ParseError != "" {
return fmt.Errorf("parse error: %s", resp.ParseError)
}
if resp.SemanticError != "" {
return fmt.Errorf("semantic error: %s", resp.SemanticError)
}
return fmt.Errorf("reload error: %s", resp.ReloadError)
}
// formatDuration formats a duration as Xd Xh Xm Xs without milliseconds. // formatDuration formats a duration as Xd Xh Xm Xs without milliseconds.
func formatDuration(d time.Duration) string { func formatDuration(d time.Duration) string {
if d < 0 { if d < 0 {

View File

@@ -67,15 +67,16 @@ func (ql *questionListener) OnChange(line []rune, pos int, key rune) (newLine []
return line, pos, false return line, pos, false
} }
// line[:pos] includes the '?' just typed — strip it before tokenizing. // Strip the '?' that was just appended to line[:pos].
before := string(line[:pos]) before := string(line[:pos])
if len(before) > 0 && before[len(before)-1] == '?' { if len(before) > 0 && before[len(before)-1] == '?' {
before = before[:len(before)-1] before = before[:len(before)-1]
} }
tokens := splitTokens(before) tokens := splitTokens(before)
var partial string // Split into confirmed prefix tokens and the partial token being typed.
var prefix []string var prefix []string
var partial string
if len(before) == 0 || before[len(before)-1] == ' ' { if len(before) == 0 || before[len(before)-1] == ' ' {
prefix = tokens prefix = tokens
partial = "" partial = ""
@@ -84,25 +85,65 @@ func (ql *questionListener) OnChange(line []rune, pos int, key rune) (newLine []
partial = tokens[len(tokens)-1] partial = tokens[len(tokens)-1]
} }
// Walk the confirmed prefix to the current node, then try to advance one
// more step using the partial token (via prefix-match or slot fallback).
// This mirrors birdc: "sh?" expands "sh" to "show" and shows show's subtree.
node, _ := Walk(ql.root, prefix)
displayPrefix := strings.Join(prefix, " ")
if partial != "" {
if next := matchFixedChild(node.Children, partial); next != nil {
// Partial uniquely matched a fixed child — descend into it.
node = next
displayPrefix = strings.Join(tokens, " ")
} else if slot := findSlotChild(node.Children); slot != nil {
// Partial is filling a slot node.
node = slot
displayPrefix = strings.Join(tokens, " ")
}
// If partial matched nothing (ambiguous or dead end), stay at the
// current node and show its subcommands with the confirmed prefix.
}
// Expand all leaf paths reachable from the current node.
lines := expandPaths(node, displayPrefix, make(map[*Node]bool))
// If the cursor is at a position where the next input is a dynamic slot,
// fetch live values now and show them below the syntax lines.
ctx, cancel := context.WithTimeout(context.Background(), completeTimeout) ctx, cancel := context.WithTimeout(context.Background(), completeTimeout)
defer cancel() defer cancel()
var dynValues []string
var dynWord string
if slot := findSlotChild(node.Children); slot != nil && slot.Dynamic != nil {
dynValues = slot.Dynamic(ctx, ql.client)
dynWord = slot.Word
}
candidates := Candidates(ql.root, prefix, partial, ctx, ql.client) // Right-align the help column at the width of the longest path + 2.
maxLen := 0
for _, l := range lines {
if len(l.path) > maxLen {
maxLen = len(l.path)
}
}
// Emit output. Raw terminal mode requires \r\n.
fmt.Fprintf(ql.rl.Stderr(), "\r\n") fmt.Fprintf(ql.rl.Stderr(), "\r\n")
if len(candidates) == 0 { if len(lines) == 0 {
fmt.Fprintf(ql.rl.Stderr(), " <no completions>\r\n") fmt.Fprintf(ql.rl.Stderr(), " <no completions>\r\n")
} else { } else {
for _, c := range candidates { for _, l := range lines {
if c.Help != "" { if l.help != "" {
fmt.Fprintf(ql.rl.Stderr(), " %-20s %s\r\n", c.Word, c.Help) fmt.Fprintf(ql.rl.Stderr(), "%-*s %s\r\n", maxLen+2, l.path, l.help)
} else { } else {
fmt.Fprintf(ql.rl.Stderr(), " %s\r\n", c.Word) fmt.Fprintf(ql.rl.Stderr(), "%s\r\n", l.path)
} }
} }
if len(dynValues) > 0 {
fmt.Fprintf(ql.rl.Stderr(), " %s: %s\r\n", dynWord, strings.Join(dynValues, " "))
}
} }
// Remove the '?' from the line and return with cursor one step back. // Remove the '?' from the line and step cursor back one position.
newLine = append(append([]rune{}, line[:pos-1]...), line[pos:]...) newLine = append(append([]rune{}, line[:pos-1]...), line[pos:]...)
return newLine, pos - 1, true return newLine, pos - 1, true
} }

View File

@@ -7,6 +7,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"strings"
"github.com/chzyer/readline" "github.com/chzyer/readline"
@@ -75,20 +76,28 @@ func dispatch(ctx context.Context, root *Node, client grpcapi.MaglevClient, toke
return node.Run(ctx, client, args) return node.Run(ctx, client, args)
} }
// showHelp prints available completions for the given token path. // showHelp prints all reachable commands from the given token path, birdc-style.
func showHelp(root *Node, tokens []string) { func showHelp(root *Node, tokens []string) {
node, _ := Walk(root, tokens) node, _ := Walk(root, tokens)
prefix := strings.Join(tokens, " ")
lines := expandPaths(node, prefix, make(map[*Node]bool))
candidates := filterFixedChildren(node.Children, "") maxLen := 0
if len(candidates) == 0 { for _, l := range lines {
if len(l.path) > maxLen {
maxLen = len(l.path)
}
}
if len(lines) == 0 {
fmt.Println(" <no completions>") fmt.Println(" <no completions>")
return return
} }
for _, c := range candidates { for _, l := range lines {
if c.Help != "" { if l.help != "" {
fmt.Printf(" %-20s %s\n", c.Word, c.Help) fmt.Printf("%-*s %s\n", maxLen+2, l.path, l.help)
} else { } else {
fmt.Printf(" %s\n", c.Word) fmt.Printf("%s\n", l.path)
} }
} }
} }

View File

@@ -91,6 +91,36 @@ func findSlotChild(children []*Node) *Node {
return nil return nil
} }
// helpLine is a (path, help) pair used when displaying '?' output.
type helpLine struct {
path string
help string
}
// expandPaths returns all (path, help) pairs for every node reachable from
// node that has a Run function. prefix is the display string accumulated so
// far (e.g. "show frontend"). visited prevents infinite loops through
// self-referencing slot nodes like watchEventsOptSlot.
func expandPaths(node *Node, prefix string, visited map[*Node]bool) []helpLine {
if visited[node] {
return nil
}
visited[node] = true
var lines []helpLine
if node.Run != nil {
lines = append(lines, helpLine{path: prefix, help: node.Help})
}
for _, child := range node.Children {
childPrefix := child.Word
if prefix != "" {
childPrefix = prefix + " " + child.Word
}
lines = append(lines, expandPaths(child, childPrefix, visited)...)
}
return lines
}
// Candidates returns the completable children at the current position given // Candidates returns the completable children at the current position given
// the already-typed tokens and the partial token being completed. // the already-typed tokens and the partial token being completed.
func Candidates(root *Node, tokens []string, partial string, ctx context.Context, client grpcapi.MaglevClient) []*Node { func Candidates(root *Node, tokens []string, partial string, ctx context.Context, client grpcapi.MaglevClient) []*Node {

153
cmd/maglevc/tree_test.go Normal file
View File

@@ -0,0 +1,153 @@
// Copyright (c) 2026, Pim van Pelt <pim@ipng.ch>
package main
import (
"strings"
"testing"
)
func TestExpandPathsRoot(t *testing.T) {
root := buildTree()
lines := expandPaths(root, "", make(map[*Node]bool))
// Should include well-known leaf paths.
want := []string{
"show version",
"show frontends",
"show frontends <name>",
"show backends",
"show backends <name>",
"show healthchecks",
"show healthchecks <name>",
"set backend <name> pause",
"set backend <name> resume",
"set backend <name> disable",
"set backend <name> enable",
"set frontend <name> pool <pool> backend <backend> weight <weight>",
"watch events",
"watch events <opt>",
"config check",
"config reload",
"quit",
"exit",
}
paths := make(map[string]bool, len(lines))
for _, l := range lines {
paths[l.path] = true
}
for _, w := range want {
if !paths[w] {
t.Errorf("expandPaths(root) missing %q", w)
}
}
}
func TestExpandPathsShow(t *testing.T) {
root := buildTree()
showNode, _ := Walk(root, []string{"show"})
lines := expandPaths(showNode, "show", make(map[*Node]bool))
for _, l := range lines {
if !strings.HasPrefix(l.path, "show ") {
t.Errorf("unexpected path %q: should start with 'show '", l.path)
}
if l.help == "" {
t.Errorf("path %q has empty help", l.path)
}
}
// version, frontends, frontends <name>, backends, backends <name>,
// healthchecks, healthchecks <name> = 7 lines
if len(lines) != 7 {
t.Errorf("expected exactly 7 show subcommands, got %d", len(lines))
}
}
func TestExpandPathsNoCycles(t *testing.T) {
root := buildTree()
// watch events has a self-referencing slot; expandPaths must terminate.
watchEvents, _ := Walk(root, []string{"watch", "events"})
lines := expandPaths(watchEvents, "watch events", make(map[*Node]bool))
// Should produce exactly 2 lines: "watch events" and "watch events <opt>".
if len(lines) != 2 {
t.Errorf("watch events: expected 2 lines, got %d: %v", len(lines), lines)
}
}
func TestExpandPathsSetBackendName(t *testing.T) {
root := buildTree()
// Walk to the name slot so displayPrefix carries the actual arg.
node, _ := Walk(root, []string{"set", "backend", "mybackend"})
lines := expandPaths(node, "set backend mybackend", make(map[*Node]bool))
want := []string{
"set backend mybackend pause",
"set backend mybackend resume",
"set backend mybackend disable",
"set backend mybackend enable",
}
if len(lines) != len(want) {
t.Fatalf("expected %d lines, got %d: %v", len(want), len(lines), lines)
}
for i, w := range want {
if lines[i].path != w {
t.Errorf("line %d: got %q, want %q", i, lines[i].path, w)
}
}
}
func TestPrefixMatchCollapsedNouns(t *testing.T) {
root := buildTree()
// "sh ba" → show backends (list all) via prefix matching.
node, args := Walk(root, []string{"sh", "ba"})
if node.Run == nil {
t.Fatal("'sh ba' did not reach a Run node")
}
if len(args) != 0 {
t.Errorf("'sh ba' should have 0 args, got %v", args)
}
// "sh ba nginx0" → show backends <name> (get specific) via slot.
node, args = Walk(root, []string{"sh", "ba", "nginx0"})
if node.Run == nil {
t.Fatal("'sh ba nginx0' did not reach a Run node")
}
if len(args) != 1 || args[0] != "nginx0" {
t.Errorf("'sh ba nginx0' args: got %v, want [nginx0]", args)
}
// "sh fr" → show frontends (list all).
node, _ = Walk(root, []string{"sh", "fr"})
if node.Run == nil {
t.Fatal("'sh fr' did not reach a Run node")
}
// "sh he icmp" → show healthchecks icmp (get specific).
node, args = Walk(root, []string{"sh", "he", "icmp"})
if node.Run == nil {
t.Fatal("'sh he icmp' did not reach a Run node")
}
if len(args) != 1 || args[0] != "icmp" {
t.Errorf("'sh he icmp' args: got %v, want [icmp]", args)
}
}
func TestExpandPathsWeightSlotWalk(t *testing.T) {
// Verify the weight command is fully walkable (fixes bug: setWeightValue
// and setFrontendPoolName were non-slot nodes that couldn't capture tokens).
root := buildTree()
node, args := Walk(root, []string{"set", "frontend", "web", "pool", "primary", "backend", "be0", "weight", "42"})
if node.Run == nil {
t.Fatal("Walk did not reach a Run node for full weight command")
}
if len(args) != 4 {
t.Errorf("expected 4 args (name, pool, backend, weight), got %d: %v", len(args), args)
}
if args[3] != "42" {
t.Errorf("args[3] (weight): got %q, want 42", args[3])
}
}

View File

@@ -93,7 +93,8 @@ func run() error {
return fmt.Errorf("listen %s: %w", *grpcAddr, err) return fmt.Errorf("listen %s: %w", *grpcAddr, err)
} }
srv := grpc.NewServer() srv := grpc.NewServer()
grpcapi.RegisterMaglevServer(srv, grpcapi.NewServer(ctx, chkr, logBroadcaster, *configPath)) maglevServer := grpcapi.NewServer(ctx, chkr, logBroadcaster, *configPath)
grpcapi.RegisterMaglevServer(srv, maglevServer)
if *enableReflection { if *enableReflection {
reflection.Register(srv) reflection.Register(srv)
} }
@@ -112,21 +113,7 @@ func run() error {
for sig := range sigCh { for sig := range sigCh {
switch sig { switch sig {
case syscall.SIGHUP: case syscall.SIGHUP:
slog.Info("config-reload-start") maglevServer.TriggerReload()
newCfg, result := config.Check(*configPath)
if !result.OK() {
if result.ParseError != "" {
slog.Error("config-check-failed", "type", "parse", "err", result.ParseError)
} else {
slog.Error("config-check-failed", "type", "semantic", "err", result.SemanticError)
}
continue
}
if err := chkr.Reload(ctx, newCfg); err != nil {
slog.Error("checker-reload-error", "err", err)
continue
}
slog.Info("config-reload-done", "frontends", len(newCfg.Frontends))
case syscall.SIGTERM, syscall.SIGINT: case syscall.SIGTERM, syscall.SIGINT:
slog.Info("shutdown", "signal", sig) slog.Info("shutdown", "signal", sig)

View File

@@ -24,7 +24,7 @@ are used for anything not set.
| Signal | Effect | | Signal | Effect |
|---|---| |---|---|
| `SIGHUP` | Reload the configuration file. The file is checked before applying; if there is a parse or semantic error the reload is aborted and the error is logged (the daemon continues running with its current config). New backends are started, removed backends are stopped, backends whose health-check config is unchanged continue probing without interruption. | | `SIGHUP` | Reload the configuration file (same code path as `config reload` in `maglevc`). The file is checked before applying; if there is a parse or semantic error the reload is aborted and the error is logged (the daemon continues running with its current config). New backends are started, removed backends are stopped, backends whose health-check config is unchanged continue probing without interruption. |
| `SIGTERM` / `SIGINT` | Graceful shutdown. Active gRPC streams are closed, the server drains, then the process exits. | | `SIGTERM` / `SIGINT` | Graceful shutdown. Active gRPC streams are closed, the server drains, then the process exits. |
### Capabilities ### Capabilities
@@ -69,33 +69,33 @@ build version is printed on entry.
``` ```
show version Print build version, commit hash, and build date. show version Print build version, commit hash, and build date.
show frontends List all frontend names. show frontends [<name>] Without name: list all frontend names.
show frontend <name> Show address, protocol, port, description, and pools. With name: show address, protocol, port, description,
Each pool lists its backends with weights (if != 100) and pools. Each pool lists its backends with weights
and marks disabled backends with [disabled]. (if != 100) and marks disabled backends with [disabled].
show backends List all backend names. show backends [<name>] Without name: list all backend names.
show backend <name> Show address, current state (with duration in that state), With name: show address, current state (with duration),
enabled flag, health check, and recent state transitions enabled flag, health check, and recent state transitions
with timestamps and how long ago each occurred. with timestamps and how long ago each occurred.
show healthchecks List all health-check names. show healthchecks [<name>] Without name: list all health-check names.
show healthcheck <name> Show full health-check configuration. With name: show full health-check configuration.
set backend <name> pause Suspend health checking for a backend, freezing its state. set backend <name> pause Suspend health checking for a backend, freezing its state.
set backend <name> resume Resume health checking; backend re-enters unknown state set backend <name> resume Resume health checking; backend re-enters unknown state
and is probed immediately. and is probed immediately.
set frontend <name> pool <pool> backend <name> weight <0-100>
Set the weight of a backend within a pool. Weight 0 keeps
the backend in the pool but assigns it no traffic.
Takes effect immediately without reloading configuration.
set backend <name> disable Stop probing entirely and remove the backend from rotation. set backend <name> disable Stop probing entirely and remove the backend from rotation.
The backend remains visible (state: removed) and can be The backend remains visible (state: removed) and can be
re-enabled without reloading configuration. re-enabled without reloading configuration.
set backend <name> enable Re-enable a disabled backend. A fresh probe goroutine is set backend <name> enable Re-enable a disabled backend. A fresh probe goroutine is
started and the backend re-enters unknown state. started and the backend re-enters unknown state.
set frontend <name> pool <pool> backend <name> weight <0-100>
Set the weight of a backend within a pool. Weight 0 keeps
the backend in the pool but assigns it no traffic.
Takes effect immediately without reloading configuration.
watch events Stream all events (log, backend transitions, frontend) watch events Stream all events (log, backend transitions, frontend)
[num <n>] Stop after receiving n events. [num <n>] Stop after receiving n events.
[log [level <level>]] Include log events. level is debug|info|warn|error [log [level <level>]] Include log events. level is debug|info|warn|error
@@ -115,6 +115,10 @@ watch events Stream all events (log, backend transitions, fr
config check Ask maglevd to read and validate its current config file. config check Ask maglevd to read and validate its current config file.
Prints "config ok" on success, or the error (parse or Prints "config ok" on success, or the error (parse or
semantic) returned by the daemon. semantic) returned by the daemon.
config reload Check and reload the configuration file. Equivalent to
sending SIGHUP to maglevd. Prints "config reloaded" on
success, or the specific error (parse, semantic, or
reload) that prevented the reload.
quit / exit Leave the interactive shell. quit / exit Leave the interactive shell.
``` ```
@@ -134,32 +138,5 @@ restored.
completions for the current position, with a short description next to each completions for the current position, with a short description next to each
keyword. The `?` character is not added to the input line. keyword. The `?` character is not added to the input line.
Commands and keywords support **prefix matching**: typing `sh b` is equivalent Commands and keywords support **prefix matching**: typing `sh ba` is equivalent
to `show backend` provided the prefix is unambiguous. Exact matches always take to `show backends`, and `sh ba nginx0` is equivalent to `show backends nginx0`.
priority over prefix matches, so `show backend` and `show backends` are
unambiguous even though one is a prefix of the other.
### Command tree and parser
Commands form a tree of `Node` values. Each node has a fixed `Word` (a keyword)
or is a *slot node* (marked by a `Dynamic` function that enumerates valid
values at completion time). The parser (`Walk`) descends the tree token by
token:
1. Try to match the current token against the fixed-keyword children of the
current node (exact match first, then unique prefix match).
2. If no fixed child matches, try a slot child — any token is accepted and
stored as an argument.
3. Stop when tokens are exhausted or no match is found.
The leaf node reached by `Walk` must have a `Run` function; otherwise the
available sub-commands at that position are printed as help. Arguments
collected from slot nodes are passed to `Run` as a slice.
Example walk for `set backend nginx0-ams pause`:
```
root → set → backend → <name>(nginx0-ams collected as arg) → pause
```
`pause.Run` is called with `args = ["nginx0-ams"]`.

View File

@@ -401,6 +401,110 @@ func (x *CheckConfigResponse) GetSemanticError() string {
return "" return ""
} }
type ReloadConfigRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *ReloadConfigRequest) Reset() {
*x = ReloadConfigRequest{}
mi := &file_proto_maglev_proto_msgTypes[9]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *ReloadConfigRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ReloadConfigRequest) ProtoMessage() {}
func (x *ReloadConfigRequest) ProtoReflect() protoreflect.Message {
mi := &file_proto_maglev_proto_msgTypes[9]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ReloadConfigRequest.ProtoReflect.Descriptor instead.
func (*ReloadConfigRequest) Descriptor() ([]byte, []int) {
return file_proto_maglev_proto_rawDescGZIP(), []int{9}
}
type ReloadConfigResponse struct {
state protoimpl.MessageState `protogen:"open.v1"`
Ok bool `protobuf:"varint,1,opt,name=ok,proto3" json:"ok,omitempty"`
ParseError string `protobuf:"bytes,2,opt,name=parse_error,json=parseError,proto3" json:"parse_error,omitempty"` // set when YAML cannot be read or parsed
SemanticError string `protobuf:"bytes,3,opt,name=semantic_error,json=semanticError,proto3" json:"semantic_error,omitempty"` // set when YAML is valid but semantically incorrect
ReloadError string `protobuf:"bytes,4,opt,name=reload_error,json=reloadError,proto3" json:"reload_error,omitempty"` // set when config is valid but the reload itself failed
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *ReloadConfigResponse) Reset() {
*x = ReloadConfigResponse{}
mi := &file_proto_maglev_proto_msgTypes[10]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *ReloadConfigResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ReloadConfigResponse) ProtoMessage() {}
func (x *ReloadConfigResponse) ProtoReflect() protoreflect.Message {
mi := &file_proto_maglev_proto_msgTypes[10]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ReloadConfigResponse.ProtoReflect.Descriptor instead.
func (*ReloadConfigResponse) Descriptor() ([]byte, []int) {
return file_proto_maglev_proto_rawDescGZIP(), []int{10}
}
func (x *ReloadConfigResponse) GetOk() bool {
if x != nil {
return x.Ok
}
return false
}
func (x *ReloadConfigResponse) GetParseError() string {
if x != nil {
return x.ParseError
}
return ""
}
func (x *ReloadConfigResponse) GetSemanticError() string {
if x != nil {
return x.SemanticError
}
return ""
}
func (x *ReloadConfigResponse) GetReloadError() string {
if x != nil {
return x.ReloadError
}
return ""
}
type SetWeightRequest struct { type SetWeightRequest struct {
state protoimpl.MessageState `protogen:"open.v1"` state protoimpl.MessageState `protogen:"open.v1"`
Frontend string `protobuf:"bytes,1,opt,name=frontend,proto3" json:"frontend,omitempty"` Frontend string `protobuf:"bytes,1,opt,name=frontend,proto3" json:"frontend,omitempty"`
@@ -413,7 +517,7 @@ type SetWeightRequest struct {
func (x *SetWeightRequest) Reset() { func (x *SetWeightRequest) Reset() {
*x = SetWeightRequest{} *x = SetWeightRequest{}
mi := &file_proto_maglev_proto_msgTypes[9] mi := &file_proto_maglev_proto_msgTypes[11]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -425,7 +529,7 @@ func (x *SetWeightRequest) String() string {
func (*SetWeightRequest) ProtoMessage() {} func (*SetWeightRequest) ProtoMessage() {}
func (x *SetWeightRequest) ProtoReflect() protoreflect.Message { func (x *SetWeightRequest) ProtoReflect() protoreflect.Message {
mi := &file_proto_maglev_proto_msgTypes[9] mi := &file_proto_maglev_proto_msgTypes[11]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@@ -438,7 +542,7 @@ func (x *SetWeightRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use SetWeightRequest.ProtoReflect.Descriptor instead. // Deprecated: Use SetWeightRequest.ProtoReflect.Descriptor instead.
func (*SetWeightRequest) Descriptor() ([]byte, []int) { func (*SetWeightRequest) Descriptor() ([]byte, []int) {
return file_proto_maglev_proto_rawDescGZIP(), []int{9} return file_proto_maglev_proto_rawDescGZIP(), []int{11}
} }
func (x *SetWeightRequest) GetFrontend() string { func (x *SetWeightRequest) GetFrontend() string {
@@ -483,7 +587,7 @@ type WatchRequest struct {
func (x *WatchRequest) Reset() { func (x *WatchRequest) Reset() {
*x = WatchRequest{} *x = WatchRequest{}
mi := &file_proto_maglev_proto_msgTypes[10] mi := &file_proto_maglev_proto_msgTypes[12]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -495,7 +599,7 @@ func (x *WatchRequest) String() string {
func (*WatchRequest) ProtoMessage() {} func (*WatchRequest) ProtoMessage() {}
func (x *WatchRequest) ProtoReflect() protoreflect.Message { func (x *WatchRequest) ProtoReflect() protoreflect.Message {
mi := &file_proto_maglev_proto_msgTypes[10] mi := &file_proto_maglev_proto_msgTypes[12]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@@ -508,7 +612,7 @@ func (x *WatchRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use WatchRequest.ProtoReflect.Descriptor instead. // Deprecated: Use WatchRequest.ProtoReflect.Descriptor instead.
func (*WatchRequest) Descriptor() ([]byte, []int) { func (*WatchRequest) Descriptor() ([]byte, []int) {
return file_proto_maglev_proto_rawDescGZIP(), []int{10} return file_proto_maglev_proto_rawDescGZIP(), []int{12}
} }
func (x *WatchRequest) GetLog() bool { func (x *WatchRequest) GetLog() bool {
@@ -548,7 +652,7 @@ type ListFrontendsResponse struct {
func (x *ListFrontendsResponse) Reset() { func (x *ListFrontendsResponse) Reset() {
*x = ListFrontendsResponse{} *x = ListFrontendsResponse{}
mi := &file_proto_maglev_proto_msgTypes[11] mi := &file_proto_maglev_proto_msgTypes[13]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -560,7 +664,7 @@ func (x *ListFrontendsResponse) String() string {
func (*ListFrontendsResponse) ProtoMessage() {} func (*ListFrontendsResponse) ProtoMessage() {}
func (x *ListFrontendsResponse) ProtoReflect() protoreflect.Message { func (x *ListFrontendsResponse) ProtoReflect() protoreflect.Message {
mi := &file_proto_maglev_proto_msgTypes[11] mi := &file_proto_maglev_proto_msgTypes[13]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@@ -573,7 +677,7 @@ func (x *ListFrontendsResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use ListFrontendsResponse.ProtoReflect.Descriptor instead. // Deprecated: Use ListFrontendsResponse.ProtoReflect.Descriptor instead.
func (*ListFrontendsResponse) Descriptor() ([]byte, []int) { func (*ListFrontendsResponse) Descriptor() ([]byte, []int) {
return file_proto_maglev_proto_rawDescGZIP(), []int{11} return file_proto_maglev_proto_rawDescGZIP(), []int{13}
} }
func (x *ListFrontendsResponse) GetFrontendNames() []string { func (x *ListFrontendsResponse) GetFrontendNames() []string {
@@ -593,7 +697,7 @@ type PoolBackendInfo struct {
func (x *PoolBackendInfo) Reset() { func (x *PoolBackendInfo) Reset() {
*x = PoolBackendInfo{} *x = PoolBackendInfo{}
mi := &file_proto_maglev_proto_msgTypes[12] mi := &file_proto_maglev_proto_msgTypes[14]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -605,7 +709,7 @@ func (x *PoolBackendInfo) String() string {
func (*PoolBackendInfo) ProtoMessage() {} func (*PoolBackendInfo) ProtoMessage() {}
func (x *PoolBackendInfo) ProtoReflect() protoreflect.Message { func (x *PoolBackendInfo) ProtoReflect() protoreflect.Message {
mi := &file_proto_maglev_proto_msgTypes[12] mi := &file_proto_maglev_proto_msgTypes[14]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@@ -618,7 +722,7 @@ func (x *PoolBackendInfo) ProtoReflect() protoreflect.Message {
// Deprecated: Use PoolBackendInfo.ProtoReflect.Descriptor instead. // Deprecated: Use PoolBackendInfo.ProtoReflect.Descriptor instead.
func (*PoolBackendInfo) Descriptor() ([]byte, []int) { func (*PoolBackendInfo) Descriptor() ([]byte, []int) {
return file_proto_maglev_proto_rawDescGZIP(), []int{12} return file_proto_maglev_proto_rawDescGZIP(), []int{14}
} }
func (x *PoolBackendInfo) GetName() string { func (x *PoolBackendInfo) GetName() string {
@@ -645,7 +749,7 @@ type PoolInfo struct {
func (x *PoolInfo) Reset() { func (x *PoolInfo) Reset() {
*x = PoolInfo{} *x = PoolInfo{}
mi := &file_proto_maglev_proto_msgTypes[13] mi := &file_proto_maglev_proto_msgTypes[15]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -657,7 +761,7 @@ func (x *PoolInfo) String() string {
func (*PoolInfo) ProtoMessage() {} func (*PoolInfo) ProtoMessage() {}
func (x *PoolInfo) ProtoReflect() protoreflect.Message { func (x *PoolInfo) ProtoReflect() protoreflect.Message {
mi := &file_proto_maglev_proto_msgTypes[13] mi := &file_proto_maglev_proto_msgTypes[15]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@@ -670,7 +774,7 @@ func (x *PoolInfo) ProtoReflect() protoreflect.Message {
// Deprecated: Use PoolInfo.ProtoReflect.Descriptor instead. // Deprecated: Use PoolInfo.ProtoReflect.Descriptor instead.
func (*PoolInfo) Descriptor() ([]byte, []int) { func (*PoolInfo) Descriptor() ([]byte, []int) {
return file_proto_maglev_proto_rawDescGZIP(), []int{13} return file_proto_maglev_proto_rawDescGZIP(), []int{15}
} }
func (x *PoolInfo) GetName() string { func (x *PoolInfo) GetName() string {
@@ -701,7 +805,7 @@ type FrontendInfo struct {
func (x *FrontendInfo) Reset() { func (x *FrontendInfo) Reset() {
*x = FrontendInfo{} *x = FrontendInfo{}
mi := &file_proto_maglev_proto_msgTypes[14] mi := &file_proto_maglev_proto_msgTypes[16]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -713,7 +817,7 @@ func (x *FrontendInfo) String() string {
func (*FrontendInfo) ProtoMessage() {} func (*FrontendInfo) ProtoMessage() {}
func (x *FrontendInfo) ProtoReflect() protoreflect.Message { func (x *FrontendInfo) ProtoReflect() protoreflect.Message {
mi := &file_proto_maglev_proto_msgTypes[14] mi := &file_proto_maglev_proto_msgTypes[16]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@@ -726,7 +830,7 @@ func (x *FrontendInfo) ProtoReflect() protoreflect.Message {
// Deprecated: Use FrontendInfo.ProtoReflect.Descriptor instead. // Deprecated: Use FrontendInfo.ProtoReflect.Descriptor instead.
func (*FrontendInfo) Descriptor() ([]byte, []int) { func (*FrontendInfo) Descriptor() ([]byte, []int) {
return file_proto_maglev_proto_rawDescGZIP(), []int{14} return file_proto_maglev_proto_rawDescGZIP(), []int{16}
} }
func (x *FrontendInfo) GetName() string { func (x *FrontendInfo) GetName() string {
@@ -780,7 +884,7 @@ type ListBackendsResponse struct {
func (x *ListBackendsResponse) Reset() { func (x *ListBackendsResponse) Reset() {
*x = ListBackendsResponse{} *x = ListBackendsResponse{}
mi := &file_proto_maglev_proto_msgTypes[15] mi := &file_proto_maglev_proto_msgTypes[17]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -792,7 +896,7 @@ func (x *ListBackendsResponse) String() string {
func (*ListBackendsResponse) ProtoMessage() {} func (*ListBackendsResponse) ProtoMessage() {}
func (x *ListBackendsResponse) ProtoReflect() protoreflect.Message { func (x *ListBackendsResponse) ProtoReflect() protoreflect.Message {
mi := &file_proto_maglev_proto_msgTypes[15] mi := &file_proto_maglev_proto_msgTypes[17]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@@ -805,7 +909,7 @@ func (x *ListBackendsResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use ListBackendsResponse.ProtoReflect.Descriptor instead. // Deprecated: Use ListBackendsResponse.ProtoReflect.Descriptor instead.
func (*ListBackendsResponse) Descriptor() ([]byte, []int) { func (*ListBackendsResponse) Descriptor() ([]byte, []int) {
return file_proto_maglev_proto_rawDescGZIP(), []int{15} return file_proto_maglev_proto_rawDescGZIP(), []int{17}
} }
func (x *ListBackendsResponse) GetBackendNames() []string { func (x *ListBackendsResponse) GetBackendNames() []string {
@@ -824,7 +928,7 @@ type ListHealthChecksResponse struct {
func (x *ListHealthChecksResponse) Reset() { func (x *ListHealthChecksResponse) Reset() {
*x = ListHealthChecksResponse{} *x = ListHealthChecksResponse{}
mi := &file_proto_maglev_proto_msgTypes[16] mi := &file_proto_maglev_proto_msgTypes[18]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -836,7 +940,7 @@ func (x *ListHealthChecksResponse) String() string {
func (*ListHealthChecksResponse) ProtoMessage() {} func (*ListHealthChecksResponse) ProtoMessage() {}
func (x *ListHealthChecksResponse) ProtoReflect() protoreflect.Message { func (x *ListHealthChecksResponse) ProtoReflect() protoreflect.Message {
mi := &file_proto_maglev_proto_msgTypes[16] mi := &file_proto_maglev_proto_msgTypes[18]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@@ -849,7 +953,7 @@ func (x *ListHealthChecksResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use ListHealthChecksResponse.ProtoReflect.Descriptor instead. // Deprecated: Use ListHealthChecksResponse.ProtoReflect.Descriptor instead.
func (*ListHealthChecksResponse) Descriptor() ([]byte, []int) { func (*ListHealthChecksResponse) Descriptor() ([]byte, []int) {
return file_proto_maglev_proto_rawDescGZIP(), []int{16} return file_proto_maglev_proto_rawDescGZIP(), []int{18}
} }
func (x *ListHealthChecksResponse) GetNames() []string { func (x *ListHealthChecksResponse) GetNames() []string {
@@ -874,7 +978,7 @@ type HTTPCheckParams struct {
func (x *HTTPCheckParams) Reset() { func (x *HTTPCheckParams) Reset() {
*x = HTTPCheckParams{} *x = HTTPCheckParams{}
mi := &file_proto_maglev_proto_msgTypes[17] mi := &file_proto_maglev_proto_msgTypes[19]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -886,7 +990,7 @@ func (x *HTTPCheckParams) String() string {
func (*HTTPCheckParams) ProtoMessage() {} func (*HTTPCheckParams) ProtoMessage() {}
func (x *HTTPCheckParams) ProtoReflect() protoreflect.Message { func (x *HTTPCheckParams) ProtoReflect() protoreflect.Message {
mi := &file_proto_maglev_proto_msgTypes[17] mi := &file_proto_maglev_proto_msgTypes[19]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@@ -899,7 +1003,7 @@ func (x *HTTPCheckParams) ProtoReflect() protoreflect.Message {
// Deprecated: Use HTTPCheckParams.ProtoReflect.Descriptor instead. // Deprecated: Use HTTPCheckParams.ProtoReflect.Descriptor instead.
func (*HTTPCheckParams) Descriptor() ([]byte, []int) { func (*HTTPCheckParams) Descriptor() ([]byte, []int) {
return file_proto_maglev_proto_rawDescGZIP(), []int{17} return file_proto_maglev_proto_rawDescGZIP(), []int{19}
} }
func (x *HTTPCheckParams) GetPath() string { func (x *HTTPCheckParams) GetPath() string {
@@ -962,7 +1066,7 @@ type TCPCheckParams struct {
func (x *TCPCheckParams) Reset() { func (x *TCPCheckParams) Reset() {
*x = TCPCheckParams{} *x = TCPCheckParams{}
mi := &file_proto_maglev_proto_msgTypes[18] mi := &file_proto_maglev_proto_msgTypes[20]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -974,7 +1078,7 @@ func (x *TCPCheckParams) String() string {
func (*TCPCheckParams) ProtoMessage() {} func (*TCPCheckParams) ProtoMessage() {}
func (x *TCPCheckParams) ProtoReflect() protoreflect.Message { func (x *TCPCheckParams) ProtoReflect() protoreflect.Message {
mi := &file_proto_maglev_proto_msgTypes[18] mi := &file_proto_maglev_proto_msgTypes[20]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@@ -987,7 +1091,7 @@ func (x *TCPCheckParams) ProtoReflect() protoreflect.Message {
// Deprecated: Use TCPCheckParams.ProtoReflect.Descriptor instead. // Deprecated: Use TCPCheckParams.ProtoReflect.Descriptor instead.
func (*TCPCheckParams) Descriptor() ([]byte, []int) { func (*TCPCheckParams) Descriptor() ([]byte, []int) {
return file_proto_maglev_proto_rawDescGZIP(), []int{18} return file_proto_maglev_proto_rawDescGZIP(), []int{20}
} }
func (x *TCPCheckParams) GetSsl() bool { func (x *TCPCheckParams) GetSsl() bool {
@@ -1032,7 +1136,7 @@ type HealthCheckInfo struct {
func (x *HealthCheckInfo) Reset() { func (x *HealthCheckInfo) Reset() {
*x = HealthCheckInfo{} *x = HealthCheckInfo{}
mi := &file_proto_maglev_proto_msgTypes[19] mi := &file_proto_maglev_proto_msgTypes[21]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -1044,7 +1148,7 @@ func (x *HealthCheckInfo) String() string {
func (*HealthCheckInfo) ProtoMessage() {} func (*HealthCheckInfo) ProtoMessage() {}
func (x *HealthCheckInfo) ProtoReflect() protoreflect.Message { func (x *HealthCheckInfo) ProtoReflect() protoreflect.Message {
mi := &file_proto_maglev_proto_msgTypes[19] mi := &file_proto_maglev_proto_msgTypes[21]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@@ -1057,7 +1161,7 @@ func (x *HealthCheckInfo) ProtoReflect() protoreflect.Message {
// Deprecated: Use HealthCheckInfo.ProtoReflect.Descriptor instead. // Deprecated: Use HealthCheckInfo.ProtoReflect.Descriptor instead.
func (*HealthCheckInfo) Descriptor() ([]byte, []int) { func (*HealthCheckInfo) Descriptor() ([]byte, []int) {
return file_proto_maglev_proto_rawDescGZIP(), []int{19} return file_proto_maglev_proto_rawDescGZIP(), []int{21}
} }
func (x *HealthCheckInfo) GetName() string { func (x *HealthCheckInfo) GetName() string {
@@ -1165,7 +1269,7 @@ type BackendInfo struct {
func (x *BackendInfo) Reset() { func (x *BackendInfo) Reset() {
*x = BackendInfo{} *x = BackendInfo{}
mi := &file_proto_maglev_proto_msgTypes[20] mi := &file_proto_maglev_proto_msgTypes[22]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -1177,7 +1281,7 @@ func (x *BackendInfo) String() string {
func (*BackendInfo) ProtoMessage() {} func (*BackendInfo) ProtoMessage() {}
func (x *BackendInfo) ProtoReflect() protoreflect.Message { func (x *BackendInfo) ProtoReflect() protoreflect.Message {
mi := &file_proto_maglev_proto_msgTypes[20] mi := &file_proto_maglev_proto_msgTypes[22]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@@ -1190,7 +1294,7 @@ func (x *BackendInfo) ProtoReflect() protoreflect.Message {
// Deprecated: Use BackendInfo.ProtoReflect.Descriptor instead. // Deprecated: Use BackendInfo.ProtoReflect.Descriptor instead.
func (*BackendInfo) Descriptor() ([]byte, []int) { func (*BackendInfo) Descriptor() ([]byte, []int) {
return file_proto_maglev_proto_rawDescGZIP(), []int{20} return file_proto_maglev_proto_rawDescGZIP(), []int{22}
} }
func (x *BackendInfo) GetName() string { func (x *BackendInfo) GetName() string {
@@ -1246,7 +1350,7 @@ type TransitionRecord struct {
func (x *TransitionRecord) Reset() { func (x *TransitionRecord) Reset() {
*x = TransitionRecord{} *x = TransitionRecord{}
mi := &file_proto_maglev_proto_msgTypes[21] mi := &file_proto_maglev_proto_msgTypes[23]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -1258,7 +1362,7 @@ func (x *TransitionRecord) String() string {
func (*TransitionRecord) ProtoMessage() {} func (*TransitionRecord) ProtoMessage() {}
func (x *TransitionRecord) ProtoReflect() protoreflect.Message { func (x *TransitionRecord) ProtoReflect() protoreflect.Message {
mi := &file_proto_maglev_proto_msgTypes[21] mi := &file_proto_maglev_proto_msgTypes[23]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@@ -1271,7 +1375,7 @@ func (x *TransitionRecord) ProtoReflect() protoreflect.Message {
// Deprecated: Use TransitionRecord.ProtoReflect.Descriptor instead. // Deprecated: Use TransitionRecord.ProtoReflect.Descriptor instead.
func (*TransitionRecord) Descriptor() ([]byte, []int) { func (*TransitionRecord) Descriptor() ([]byte, []int) {
return file_proto_maglev_proto_rawDescGZIP(), []int{21} return file_proto_maglev_proto_rawDescGZIP(), []int{23}
} }
func (x *TransitionRecord) GetFrom() string { func (x *TransitionRecord) GetFrom() string {
@@ -1306,7 +1410,7 @@ type LogAttr struct {
func (x *LogAttr) Reset() { func (x *LogAttr) Reset() {
*x = LogAttr{} *x = LogAttr{}
mi := &file_proto_maglev_proto_msgTypes[22] mi := &file_proto_maglev_proto_msgTypes[24]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -1318,7 +1422,7 @@ func (x *LogAttr) String() string {
func (*LogAttr) ProtoMessage() {} func (*LogAttr) ProtoMessage() {}
func (x *LogAttr) ProtoReflect() protoreflect.Message { func (x *LogAttr) ProtoReflect() protoreflect.Message {
mi := &file_proto_maglev_proto_msgTypes[22] mi := &file_proto_maglev_proto_msgTypes[24]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@@ -1331,7 +1435,7 @@ func (x *LogAttr) ProtoReflect() protoreflect.Message {
// Deprecated: Use LogAttr.ProtoReflect.Descriptor instead. // Deprecated: Use LogAttr.ProtoReflect.Descriptor instead.
func (*LogAttr) Descriptor() ([]byte, []int) { func (*LogAttr) Descriptor() ([]byte, []int) {
return file_proto_maglev_proto_rawDescGZIP(), []int{22} return file_proto_maglev_proto_rawDescGZIP(), []int{24}
} }
func (x *LogAttr) GetKey() string { func (x *LogAttr) GetKey() string {
@@ -1361,7 +1465,7 @@ type LogEvent struct {
func (x *LogEvent) Reset() { func (x *LogEvent) Reset() {
*x = LogEvent{} *x = LogEvent{}
mi := &file_proto_maglev_proto_msgTypes[23] mi := &file_proto_maglev_proto_msgTypes[25]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -1373,7 +1477,7 @@ func (x *LogEvent) String() string {
func (*LogEvent) ProtoMessage() {} func (*LogEvent) ProtoMessage() {}
func (x *LogEvent) ProtoReflect() protoreflect.Message { func (x *LogEvent) ProtoReflect() protoreflect.Message {
mi := &file_proto_maglev_proto_msgTypes[23] mi := &file_proto_maglev_proto_msgTypes[25]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@@ -1386,7 +1490,7 @@ func (x *LogEvent) ProtoReflect() protoreflect.Message {
// Deprecated: Use LogEvent.ProtoReflect.Descriptor instead. // Deprecated: Use LogEvent.ProtoReflect.Descriptor instead.
func (*LogEvent) Descriptor() ([]byte, []int) { func (*LogEvent) Descriptor() ([]byte, []int) {
return file_proto_maglev_proto_rawDescGZIP(), []int{23} return file_proto_maglev_proto_rawDescGZIP(), []int{25}
} }
func (x *LogEvent) GetAtUnixNs() int64 { func (x *LogEvent) GetAtUnixNs() int64 {
@@ -1428,7 +1532,7 @@ type BackendEvent struct {
func (x *BackendEvent) Reset() { func (x *BackendEvent) Reset() {
*x = BackendEvent{} *x = BackendEvent{}
mi := &file_proto_maglev_proto_msgTypes[24] mi := &file_proto_maglev_proto_msgTypes[26]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -1440,7 +1544,7 @@ func (x *BackendEvent) String() string {
func (*BackendEvent) ProtoMessage() {} func (*BackendEvent) ProtoMessage() {}
func (x *BackendEvent) ProtoReflect() protoreflect.Message { func (x *BackendEvent) ProtoReflect() protoreflect.Message {
mi := &file_proto_maglev_proto_msgTypes[24] mi := &file_proto_maglev_proto_msgTypes[26]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@@ -1453,7 +1557,7 @@ func (x *BackendEvent) ProtoReflect() protoreflect.Message {
// Deprecated: Use BackendEvent.ProtoReflect.Descriptor instead. // Deprecated: Use BackendEvent.ProtoReflect.Descriptor instead.
func (*BackendEvent) Descriptor() ([]byte, []int) { func (*BackendEvent) Descriptor() ([]byte, []int) {
return file_proto_maglev_proto_rawDescGZIP(), []int{24} return file_proto_maglev_proto_rawDescGZIP(), []int{26}
} }
func (x *BackendEvent) GetBackendName() string { func (x *BackendEvent) GetBackendName() string {
@@ -1479,7 +1583,7 @@ type FrontendEvent struct {
func (x *FrontendEvent) Reset() { func (x *FrontendEvent) Reset() {
*x = FrontendEvent{} *x = FrontendEvent{}
mi := &file_proto_maglev_proto_msgTypes[25] mi := &file_proto_maglev_proto_msgTypes[27]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -1491,7 +1595,7 @@ func (x *FrontendEvent) String() string {
func (*FrontendEvent) ProtoMessage() {} func (*FrontendEvent) ProtoMessage() {}
func (x *FrontendEvent) ProtoReflect() protoreflect.Message { func (x *FrontendEvent) ProtoReflect() protoreflect.Message {
mi := &file_proto_maglev_proto_msgTypes[25] mi := &file_proto_maglev_proto_msgTypes[27]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@@ -1504,7 +1608,7 @@ func (x *FrontendEvent) ProtoReflect() protoreflect.Message {
// Deprecated: Use FrontendEvent.ProtoReflect.Descriptor instead. // Deprecated: Use FrontendEvent.ProtoReflect.Descriptor instead.
func (*FrontendEvent) Descriptor() ([]byte, []int) { func (*FrontendEvent) Descriptor() ([]byte, []int) {
return file_proto_maglev_proto_rawDescGZIP(), []int{25} return file_proto_maglev_proto_rawDescGZIP(), []int{27}
} }
// Event is the envelope returned by WatchEvents. // Event is the envelope returned by WatchEvents.
@@ -1522,7 +1626,7 @@ type Event struct {
func (x *Event) Reset() { func (x *Event) Reset() {
*x = Event{} *x = Event{}
mi := &file_proto_maglev_proto_msgTypes[26] mi := &file_proto_maglev_proto_msgTypes[28]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -1534,7 +1638,7 @@ func (x *Event) String() string {
func (*Event) ProtoMessage() {} func (*Event) ProtoMessage() {}
func (x *Event) ProtoReflect() protoreflect.Message { func (x *Event) ProtoReflect() protoreflect.Message {
mi := &file_proto_maglev_proto_msgTypes[26] mi := &file_proto_maglev_proto_msgTypes[28]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@@ -1547,7 +1651,7 @@ func (x *Event) ProtoReflect() protoreflect.Message {
// Deprecated: Use Event.ProtoReflect.Descriptor instead. // Deprecated: Use Event.ProtoReflect.Descriptor instead.
func (*Event) Descriptor() ([]byte, []int) { func (*Event) Descriptor() ([]byte, []int) {
return file_proto_maglev_proto_rawDescGZIP(), []int{26} return file_proto_maglev_proto_rawDescGZIP(), []int{28}
} }
func (x *Event) GetEvent() isEvent_Event { func (x *Event) GetEvent() isEvent_Event {
@@ -1627,7 +1731,14 @@ const file_proto_maglev_proto_rawDesc = "" +
"\x02ok\x18\x01 \x01(\bR\x02ok\x12\x1f\n" + "\x02ok\x18\x01 \x01(\bR\x02ok\x12\x1f\n" +
"\vparse_error\x18\x02 \x01(\tR\n" + "\vparse_error\x18\x02 \x01(\tR\n" +
"parseError\x12%\n" + "parseError\x12%\n" +
"\x0esemantic_error\x18\x03 \x01(\tR\rsemanticError\"t\n" + "\x0esemantic_error\x18\x03 \x01(\tR\rsemanticError\"\x15\n" +
"\x13ReloadConfigRequest\"\x91\x01\n" +
"\x14ReloadConfigResponse\x12\x0e\n" +
"\x02ok\x18\x01 \x01(\bR\x02ok\x12\x1f\n" +
"\vparse_error\x18\x02 \x01(\tR\n" +
"parseError\x12%\n" +
"\x0esemantic_error\x18\x03 \x01(\tR\rsemanticError\x12!\n" +
"\freload_error\x18\x04 \x01(\tR\vreloadError\"t\n" +
"\x10SetWeightRequest\x12\x1a\n" + "\x10SetWeightRequest\x12\x1a\n" +
"\bfrontend\x18\x01 \x01(\tR\bfrontend\x12\x12\n" + "\bfrontend\x18\x01 \x01(\tR\bfrontend\x12\x12\n" +
"\x04pool\x18\x02 \x01(\tR\x04pool\x12\x18\n" + "\x04pool\x18\x02 \x01(\tR\x04pool\x12\x18\n" +
@@ -1723,7 +1834,7 @@ const file_proto_maglev_proto_rawDesc = "" +
"\x03log\x18\x01 \x01(\v2\x10.maglev.LogEventH\x00R\x03log\x120\n" + "\x03log\x18\x01 \x01(\v2\x10.maglev.LogEventH\x00R\x03log\x120\n" +
"\abackend\x18\x02 \x01(\v2\x14.maglev.BackendEventH\x00R\abackend\x123\n" + "\abackend\x18\x02 \x01(\v2\x14.maglev.BackendEventH\x00R\abackend\x123\n" +
"\bfrontend\x18\x03 \x01(\v2\x15.maglev.FrontendEventH\x00R\bfrontendB\a\n" + "\bfrontend\x18\x03 \x01(\v2\x15.maglev.FrontendEventH\x00R\bfrontendB\a\n" +
"\x05event2\x87\a\n" + "\x05event2\xd2\a\n" +
"\x06Maglev\x12L\n" + "\x06Maglev\x12L\n" +
"\rListFrontends\x12\x1c.maglev.ListFrontendsRequest\x1a\x1d.maglev.ListFrontendsResponse\x12?\n" + "\rListFrontends\x12\x1c.maglev.ListFrontendsRequest\x1a\x1d.maglev.ListFrontendsResponse\x12?\n" +
"\vGetFrontend\x12\x1a.maglev.GetFrontendRequest\x1a\x14.maglev.FrontendInfo\x12I\n" + "\vGetFrontend\x12\x1a.maglev.GetFrontendRequest\x1a\x14.maglev.FrontendInfo\x12I\n" +
@@ -1738,7 +1849,8 @@ const file_proto_maglev_proto_rawDesc = "" +
"\x0eGetHealthCheck\x12\x1d.maglev.GetHealthCheckRequest\x1a\x17.maglev.HealthCheckInfo\x12N\n" + "\x0eGetHealthCheck\x12\x1d.maglev.GetHealthCheckRequest\x1a\x17.maglev.HealthCheckInfo\x12N\n" +
"\x1cSetFrontendPoolBackendWeight\x12\x18.maglev.SetWeightRequest\x1a\x14.maglev.FrontendInfo\x124\n" + "\x1cSetFrontendPoolBackendWeight\x12\x18.maglev.SetWeightRequest\x1a\x14.maglev.FrontendInfo\x124\n" +
"\vWatchEvents\x12\x14.maglev.WatchRequest\x1a\r.maglev.Event0\x01\x12F\n" + "\vWatchEvents\x12\x14.maglev.WatchRequest\x1a\r.maglev.Event0\x01\x12F\n" +
"\vCheckConfig\x12\x1a.maglev.CheckConfigRequest\x1a\x1b.maglev.CheckConfigResponseB.Z,git.ipng.ch/ipng/vpp-maglev/internal/grpcapib\x06proto3" "\vCheckConfig\x12\x1a.maglev.CheckConfigRequest\x1a\x1b.maglev.CheckConfigResponse\x12I\n" +
"\fReloadConfig\x12\x1b.maglev.ReloadConfigRequest\x1a\x1c.maglev.ReloadConfigResponseB.Z,git.ipng.ch/ipng/vpp-maglev/internal/grpcapib\x06proto3"
var ( var (
file_proto_maglev_proto_rawDescOnce sync.Once file_proto_maglev_proto_rawDescOnce sync.Once
@@ -1752,7 +1864,7 @@ func file_proto_maglev_proto_rawDescGZIP() []byte {
return file_proto_maglev_proto_rawDescData return file_proto_maglev_proto_rawDescData
} }
var file_proto_maglev_proto_msgTypes = make([]protoimpl.MessageInfo, 27) var file_proto_maglev_proto_msgTypes = make([]protoimpl.MessageInfo, 29)
var file_proto_maglev_proto_goTypes = []any{ var file_proto_maglev_proto_goTypes = []any{
(*ListFrontendsRequest)(nil), // 0: maglev.ListFrontendsRequest (*ListFrontendsRequest)(nil), // 0: maglev.ListFrontendsRequest
(*GetFrontendRequest)(nil), // 1: maglev.GetFrontendRequest (*GetFrontendRequest)(nil), // 1: maglev.GetFrontendRequest
@@ -1763,36 +1875,38 @@ var file_proto_maglev_proto_goTypes = []any{
(*GetHealthCheckRequest)(nil), // 6: maglev.GetHealthCheckRequest (*GetHealthCheckRequest)(nil), // 6: maglev.GetHealthCheckRequest
(*CheckConfigRequest)(nil), // 7: maglev.CheckConfigRequest (*CheckConfigRequest)(nil), // 7: maglev.CheckConfigRequest
(*CheckConfigResponse)(nil), // 8: maglev.CheckConfigResponse (*CheckConfigResponse)(nil), // 8: maglev.CheckConfigResponse
(*SetWeightRequest)(nil), // 9: maglev.SetWeightRequest (*ReloadConfigRequest)(nil), // 9: maglev.ReloadConfigRequest
(*WatchRequest)(nil), // 10: maglev.WatchRequest (*ReloadConfigResponse)(nil), // 10: maglev.ReloadConfigResponse
(*ListFrontendsResponse)(nil), // 11: maglev.ListFrontendsResponse (*SetWeightRequest)(nil), // 11: maglev.SetWeightRequest
(*PoolBackendInfo)(nil), // 12: maglev.PoolBackendInfo (*WatchRequest)(nil), // 12: maglev.WatchRequest
(*PoolInfo)(nil), // 13: maglev.PoolInfo (*ListFrontendsResponse)(nil), // 13: maglev.ListFrontendsResponse
(*FrontendInfo)(nil), // 14: maglev.FrontendInfo (*PoolBackendInfo)(nil), // 14: maglev.PoolBackendInfo
(*ListBackendsResponse)(nil), // 15: maglev.ListBackendsResponse (*PoolInfo)(nil), // 15: maglev.PoolInfo
(*ListHealthChecksResponse)(nil), // 16: maglev.ListHealthChecksResponse (*FrontendInfo)(nil), // 16: maglev.FrontendInfo
(*HTTPCheckParams)(nil), // 17: maglev.HTTPCheckParams (*ListBackendsResponse)(nil), // 17: maglev.ListBackendsResponse
(*TCPCheckParams)(nil), // 18: maglev.TCPCheckParams (*ListHealthChecksResponse)(nil), // 18: maglev.ListHealthChecksResponse
(*HealthCheckInfo)(nil), // 19: maglev.HealthCheckInfo (*HTTPCheckParams)(nil), // 19: maglev.HTTPCheckParams
(*BackendInfo)(nil), // 20: maglev.BackendInfo (*TCPCheckParams)(nil), // 20: maglev.TCPCheckParams
(*TransitionRecord)(nil), // 21: maglev.TransitionRecord (*HealthCheckInfo)(nil), // 21: maglev.HealthCheckInfo
(*LogAttr)(nil), // 22: maglev.LogAttr (*BackendInfo)(nil), // 22: maglev.BackendInfo
(*LogEvent)(nil), // 23: maglev.LogEvent (*TransitionRecord)(nil), // 23: maglev.TransitionRecord
(*BackendEvent)(nil), // 24: maglev.BackendEvent (*LogAttr)(nil), // 24: maglev.LogAttr
(*FrontendEvent)(nil), // 25: maglev.FrontendEvent (*LogEvent)(nil), // 25: maglev.LogEvent
(*Event)(nil), // 26: maglev.Event (*BackendEvent)(nil), // 26: maglev.BackendEvent
(*FrontendEvent)(nil), // 27: maglev.FrontendEvent
(*Event)(nil), // 28: maglev.Event
} }
var file_proto_maglev_proto_depIdxs = []int32{ var file_proto_maglev_proto_depIdxs = []int32{
12, // 0: maglev.PoolInfo.backends:type_name -> maglev.PoolBackendInfo 14, // 0: maglev.PoolInfo.backends:type_name -> maglev.PoolBackendInfo
13, // 1: maglev.FrontendInfo.pools:type_name -> maglev.PoolInfo 15, // 1: maglev.FrontendInfo.pools:type_name -> maglev.PoolInfo
17, // 2: maglev.HealthCheckInfo.http:type_name -> maglev.HTTPCheckParams 19, // 2: maglev.HealthCheckInfo.http:type_name -> maglev.HTTPCheckParams
18, // 3: maglev.HealthCheckInfo.tcp:type_name -> maglev.TCPCheckParams 20, // 3: maglev.HealthCheckInfo.tcp:type_name -> maglev.TCPCheckParams
21, // 4: maglev.BackendInfo.transitions:type_name -> maglev.TransitionRecord 23, // 4: maglev.BackendInfo.transitions:type_name -> maglev.TransitionRecord
22, // 5: maglev.LogEvent.attrs:type_name -> maglev.LogAttr 24, // 5: maglev.LogEvent.attrs:type_name -> maglev.LogAttr
21, // 6: maglev.BackendEvent.transition:type_name -> maglev.TransitionRecord 23, // 6: maglev.BackendEvent.transition:type_name -> maglev.TransitionRecord
23, // 7: maglev.Event.log:type_name -> maglev.LogEvent 25, // 7: maglev.Event.log:type_name -> maglev.LogEvent
24, // 8: maglev.Event.backend:type_name -> maglev.BackendEvent 26, // 8: maglev.Event.backend:type_name -> maglev.BackendEvent
25, // 9: maglev.Event.frontend:type_name -> maglev.FrontendEvent 27, // 9: maglev.Event.frontend:type_name -> maglev.FrontendEvent
0, // 10: maglev.Maglev.ListFrontends:input_type -> maglev.ListFrontendsRequest 0, // 10: maglev.Maglev.ListFrontends:input_type -> maglev.ListFrontendsRequest
1, // 11: maglev.Maglev.GetFrontend:input_type -> maglev.GetFrontendRequest 1, // 11: maglev.Maglev.GetFrontend:input_type -> maglev.GetFrontendRequest
2, // 12: maglev.Maglev.ListBackends:input_type -> maglev.ListBackendsRequest 2, // 12: maglev.Maglev.ListBackends:input_type -> maglev.ListBackendsRequest
@@ -1803,24 +1917,26 @@ var file_proto_maglev_proto_depIdxs = []int32{
4, // 17: maglev.Maglev.DisableBackend:input_type -> maglev.BackendRequest 4, // 17: maglev.Maglev.DisableBackend:input_type -> maglev.BackendRequest
5, // 18: maglev.Maglev.ListHealthChecks:input_type -> maglev.ListHealthChecksRequest 5, // 18: maglev.Maglev.ListHealthChecks:input_type -> maglev.ListHealthChecksRequest
6, // 19: maglev.Maglev.GetHealthCheck:input_type -> maglev.GetHealthCheckRequest 6, // 19: maglev.Maglev.GetHealthCheck:input_type -> maglev.GetHealthCheckRequest
9, // 20: maglev.Maglev.SetFrontendPoolBackendWeight:input_type -> maglev.SetWeightRequest 11, // 20: maglev.Maglev.SetFrontendPoolBackendWeight:input_type -> maglev.SetWeightRequest
10, // 21: maglev.Maglev.WatchEvents:input_type -> maglev.WatchRequest 12, // 21: maglev.Maglev.WatchEvents:input_type -> maglev.WatchRequest
7, // 22: maglev.Maglev.CheckConfig:input_type -> maglev.CheckConfigRequest 7, // 22: maglev.Maglev.CheckConfig:input_type -> maglev.CheckConfigRequest
11, // 23: maglev.Maglev.ListFrontends:output_type -> maglev.ListFrontendsResponse 9, // 23: maglev.Maglev.ReloadConfig:input_type -> maglev.ReloadConfigRequest
14, // 24: maglev.Maglev.GetFrontend:output_type -> maglev.FrontendInfo 13, // 24: maglev.Maglev.ListFrontends:output_type -> maglev.ListFrontendsResponse
15, // 25: maglev.Maglev.ListBackends:output_type -> maglev.ListBackendsResponse 16, // 25: maglev.Maglev.GetFrontend:output_type -> maglev.FrontendInfo
20, // 26: maglev.Maglev.GetBackend:output_type -> maglev.BackendInfo 17, // 26: maglev.Maglev.ListBackends:output_type -> maglev.ListBackendsResponse
20, // 27: maglev.Maglev.PauseBackend:output_type -> maglev.BackendInfo 22, // 27: maglev.Maglev.GetBackend:output_type -> maglev.BackendInfo
20, // 28: maglev.Maglev.ResumeBackend:output_type -> maglev.BackendInfo 22, // 28: maglev.Maglev.PauseBackend:output_type -> maglev.BackendInfo
20, // 29: maglev.Maglev.EnableBackend:output_type -> maglev.BackendInfo 22, // 29: maglev.Maglev.ResumeBackend:output_type -> maglev.BackendInfo
20, // 30: maglev.Maglev.DisableBackend:output_type -> maglev.BackendInfo 22, // 30: maglev.Maglev.EnableBackend:output_type -> maglev.BackendInfo
16, // 31: maglev.Maglev.ListHealthChecks:output_type -> maglev.ListHealthChecksResponse 22, // 31: maglev.Maglev.DisableBackend:output_type -> maglev.BackendInfo
19, // 32: maglev.Maglev.GetHealthCheck:output_type -> maglev.HealthCheckInfo 18, // 32: maglev.Maglev.ListHealthChecks:output_type -> maglev.ListHealthChecksResponse
14, // 33: maglev.Maglev.SetFrontendPoolBackendWeight:output_type -> maglev.FrontendInfo 21, // 33: maglev.Maglev.GetHealthCheck:output_type -> maglev.HealthCheckInfo
26, // 34: maglev.Maglev.WatchEvents:output_type -> maglev.Event 16, // 34: maglev.Maglev.SetFrontendPoolBackendWeight:output_type -> maglev.FrontendInfo
8, // 35: maglev.Maglev.CheckConfig:output_type -> maglev.CheckConfigResponse 28, // 35: maglev.Maglev.WatchEvents:output_type -> maglev.Event
23, // [23:36] is the sub-list for method output_type 8, // 36: maglev.Maglev.CheckConfig:output_type -> maglev.CheckConfigResponse
10, // [10:23] is the sub-list for method input_type 10, // 37: maglev.Maglev.ReloadConfig:output_type -> maglev.ReloadConfigResponse
24, // [24:38] is the sub-list for method output_type
10, // [10:24] is the sub-list for method input_type
10, // [10:10] is the sub-list for extension type_name 10, // [10:10] is the sub-list for extension type_name
10, // [10:10] is the sub-list for extension extendee 10, // [10:10] is the sub-list for extension extendee
0, // [0:10] is the sub-list for field type_name 0, // [0:10] is the sub-list for field type_name
@@ -1831,8 +1947,8 @@ func file_proto_maglev_proto_init() {
if File_proto_maglev_proto != nil { if File_proto_maglev_proto != nil {
return return
} }
file_proto_maglev_proto_msgTypes[10].OneofWrappers = []any{} file_proto_maglev_proto_msgTypes[12].OneofWrappers = []any{}
file_proto_maglev_proto_msgTypes[26].OneofWrappers = []any{ file_proto_maglev_proto_msgTypes[28].OneofWrappers = []any{
(*Event_Log)(nil), (*Event_Log)(nil),
(*Event_Backend)(nil), (*Event_Backend)(nil),
(*Event_Frontend)(nil), (*Event_Frontend)(nil),
@@ -1843,7 +1959,7 @@ func file_proto_maglev_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(), GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_proto_maglev_proto_rawDesc), len(file_proto_maglev_proto_rawDesc)), RawDescriptor: unsafe.Slice(unsafe.StringData(file_proto_maglev_proto_rawDesc), len(file_proto_maglev_proto_rawDesc)),
NumEnums: 0, NumEnums: 0,
NumMessages: 27, NumMessages: 29,
NumExtensions: 0, NumExtensions: 0,
NumServices: 1, NumServices: 1,
}, },

View File

@@ -32,6 +32,7 @@ const (
Maglev_SetFrontendPoolBackendWeight_FullMethodName = "/maglev.Maglev/SetFrontendPoolBackendWeight" Maglev_SetFrontendPoolBackendWeight_FullMethodName = "/maglev.Maglev/SetFrontendPoolBackendWeight"
Maglev_WatchEvents_FullMethodName = "/maglev.Maglev/WatchEvents" Maglev_WatchEvents_FullMethodName = "/maglev.Maglev/WatchEvents"
Maglev_CheckConfig_FullMethodName = "/maglev.Maglev/CheckConfig" Maglev_CheckConfig_FullMethodName = "/maglev.Maglev/CheckConfig"
Maglev_ReloadConfig_FullMethodName = "/maglev.Maglev/ReloadConfig"
) )
// MaglevClient is the client API for Maglev service. // MaglevClient is the client API for Maglev service.
@@ -53,6 +54,7 @@ type MaglevClient interface {
SetFrontendPoolBackendWeight(ctx context.Context, in *SetWeightRequest, opts ...grpc.CallOption) (*FrontendInfo, error) SetFrontendPoolBackendWeight(ctx context.Context, in *SetWeightRequest, opts ...grpc.CallOption) (*FrontendInfo, error)
WatchEvents(ctx context.Context, in *WatchRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[Event], error) WatchEvents(ctx context.Context, in *WatchRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[Event], error)
CheckConfig(ctx context.Context, in *CheckConfigRequest, opts ...grpc.CallOption) (*CheckConfigResponse, error) CheckConfig(ctx context.Context, in *CheckConfigRequest, opts ...grpc.CallOption) (*CheckConfigResponse, error)
ReloadConfig(ctx context.Context, in *ReloadConfigRequest, opts ...grpc.CallOption) (*ReloadConfigResponse, error)
} }
type maglevClient struct { type maglevClient struct {
@@ -202,6 +204,16 @@ func (c *maglevClient) CheckConfig(ctx context.Context, in *CheckConfigRequest,
return out, nil return out, nil
} }
func (c *maglevClient) ReloadConfig(ctx context.Context, in *ReloadConfigRequest, opts ...grpc.CallOption) (*ReloadConfigResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(ReloadConfigResponse)
err := c.cc.Invoke(ctx, Maglev_ReloadConfig_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
// MaglevServer is the server API for Maglev service. // MaglevServer is the server API for Maglev service.
// All implementations must embed UnimplementedMaglevServer // All implementations must embed UnimplementedMaglevServer
// for forward compatibility. // for forward compatibility.
@@ -221,6 +233,7 @@ type MaglevServer interface {
SetFrontendPoolBackendWeight(context.Context, *SetWeightRequest) (*FrontendInfo, error) SetFrontendPoolBackendWeight(context.Context, *SetWeightRequest) (*FrontendInfo, error)
WatchEvents(*WatchRequest, grpc.ServerStreamingServer[Event]) error WatchEvents(*WatchRequest, grpc.ServerStreamingServer[Event]) error
CheckConfig(context.Context, *CheckConfigRequest) (*CheckConfigResponse, error) CheckConfig(context.Context, *CheckConfigRequest) (*CheckConfigResponse, error)
ReloadConfig(context.Context, *ReloadConfigRequest) (*ReloadConfigResponse, error)
mustEmbedUnimplementedMaglevServer() mustEmbedUnimplementedMaglevServer()
} }
@@ -270,6 +283,9 @@ func (UnimplementedMaglevServer) WatchEvents(*WatchRequest, grpc.ServerStreaming
func (UnimplementedMaglevServer) CheckConfig(context.Context, *CheckConfigRequest) (*CheckConfigResponse, error) { func (UnimplementedMaglevServer) CheckConfig(context.Context, *CheckConfigRequest) (*CheckConfigResponse, error) {
return nil, status.Error(codes.Unimplemented, "method CheckConfig not implemented") return nil, status.Error(codes.Unimplemented, "method CheckConfig not implemented")
} }
func (UnimplementedMaglevServer) ReloadConfig(context.Context, *ReloadConfigRequest) (*ReloadConfigResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ReloadConfig not implemented")
}
func (UnimplementedMaglevServer) mustEmbedUnimplementedMaglevServer() {} func (UnimplementedMaglevServer) mustEmbedUnimplementedMaglevServer() {}
func (UnimplementedMaglevServer) testEmbeddedByValue() {} func (UnimplementedMaglevServer) testEmbeddedByValue() {}
@@ -518,6 +534,24 @@ func _Maglev_CheckConfig_Handler(srv interface{}, ctx context.Context, dec func(
return interceptor(ctx, in, info, handler) return interceptor(ctx, in, info, handler)
} }
func _Maglev_ReloadConfig_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ReloadConfigRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(MaglevServer).ReloadConfig(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: Maglev_ReloadConfig_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(MaglevServer).ReloadConfig(ctx, req.(*ReloadConfigRequest))
}
return interceptor(ctx, in, info, handler)
}
// Maglev_ServiceDesc is the grpc.ServiceDesc for Maglev service. // Maglev_ServiceDesc is the grpc.ServiceDesc for Maglev service.
// It's only intended for direct use with grpc.RegisterService, // It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy) // and not to be introspected or modified (even as a copy)
@@ -573,6 +607,10 @@ var Maglev_ServiceDesc = grpc.ServiceDesc{
MethodName: "CheckConfig", MethodName: "CheckConfig",
Handler: _Maglev_CheckConfig_Handler, Handler: _Maglev_CheckConfig_Handler,
}, },
{
MethodName: "ReloadConfig",
Handler: _Maglev_ReloadConfig_Handler,
},
}, },
Streams: []grpc.StreamDesc{ Streams: []grpc.StreamDesc{
{ {

View File

@@ -204,12 +204,57 @@ func (s *Server) WatchEvents(req *WatchRequest, stream Maglev_WatchEventsServer)
// CheckConfig reads and validates the configuration file, returning a // CheckConfig reads and validates the configuration file, returning a
// structured result that distinguishes YAML parse errors from semantic errors. // structured result that distinguishes YAML parse errors from semantic errors.
func (s *Server) CheckConfig(_ context.Context, _ *CheckConfigRequest) (*CheckConfigResponse, error) { func (s *Server) CheckConfig(_ context.Context, _ *CheckConfigRequest) (*CheckConfigResponse, error) {
slog.Info("config-check-start", "path", s.configPath)
_, result := config.Check(s.configPath) _, result := config.Check(s.configPath)
return &CheckConfigResponse{ resp := &CheckConfigResponse{
Ok: result.OK(), Ok: result.OK(),
ParseError: result.ParseError, ParseError: result.ParseError,
SemanticError: result.SemanticError, SemanticError: result.SemanticError,
}, nil }
if result.OK() {
slog.Info("config-check-done", "result", "ok")
} else if result.ParseError != "" {
slog.Info("config-check-done", "result", "failed", "type", "parse", "err", result.ParseError)
} else {
slog.Info("config-check-done", "result", "failed", "type", "semantic", "err", result.SemanticError)
}
return resp, nil
}
// ReloadConfig checks the configuration file and, if valid, applies it to the
// running checker. This is the same code path used by SIGHUP.
func (s *Server) ReloadConfig(_ context.Context, _ *ReloadConfigRequest) (*ReloadConfigResponse, error) {
return s.doReloadConfig(), nil
}
// TriggerReload performs a config check and reload. Intended for use by the
// SIGHUP handler so that signals and gRPC share the same code path.
func (s *Server) TriggerReload() {
s.doReloadConfig()
}
func (s *Server) doReloadConfig() *ReloadConfigResponse {
slog.Info("config-reload-start")
newCfg, result := config.Check(s.configPath)
if !result.OK() {
if result.ParseError != "" {
slog.Error("config-check-failed", "type", "parse", "err", result.ParseError)
} else {
slog.Error("config-check-failed", "type", "semantic", "err", result.SemanticError)
}
return &ReloadConfigResponse{
ParseError: result.ParseError,
SemanticError: result.SemanticError,
}
}
if err := s.checker.Reload(s.ctx, newCfg); err != nil {
slog.Error("checker-reload-error", "err", err)
return &ReloadConfigResponse{
ReloadError: err.Error(),
}
}
slog.Info("config-reload-done", "frontends", len(newCfg.Frontends))
return &ReloadConfigResponse{Ok: true}
} }
// ---- conversion helpers ---------------------------------------------------- // ---- conversion helpers ----------------------------------------------------

View File

@@ -19,6 +19,7 @@ service Maglev {
rpc SetFrontendPoolBackendWeight(SetWeightRequest) returns (FrontendInfo); rpc SetFrontendPoolBackendWeight(SetWeightRequest) returns (FrontendInfo);
rpc WatchEvents(WatchRequest) returns (stream Event); rpc WatchEvents(WatchRequest) returns (stream Event);
rpc CheckConfig(CheckConfigRequest) returns (CheckConfigResponse); rpc CheckConfig(CheckConfigRequest) returns (CheckConfigResponse);
rpc ReloadConfig(ReloadConfigRequest) returns (ReloadConfigResponse);
} }
// ---- requests --------------------------------------------------------------- // ---- requests ---------------------------------------------------------------
@@ -53,6 +54,15 @@ message CheckConfigResponse {
string semantic_error = 3; // set when YAML is valid but semantically incorrect string semantic_error = 3; // set when YAML is valid but semantically incorrect
} }
message ReloadConfigRequest {}
message ReloadConfigResponse {
bool ok = 1;
string parse_error = 2; // set when YAML cannot be read or parsed
string semantic_error = 3; // set when YAML is valid but semantically incorrect
string reload_error = 4; // set when config is valid but the reload itself failed
}
message SetWeightRequest { message SetWeightRequest {
string frontend = 1; string frontend = 1;
string pool = 2; string pool = 2;