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:
@@ -6,6 +6,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
"time"
|
||||
@@ -83,8 +84,8 @@ func buildTree() *Node {
|
||||
// set backend <name> pause|resume|disabled|enabled
|
||||
setPause := &Node{Word: "pause", Help: "pause health checking", Run: runPauseBackend}
|
||||
setResume := &Node{Word: "resume", Help: "resume health checking", Run: runResumeBackend}
|
||||
setDisabled := &Node{Word: "disabled", Help: "disable backend (not implemented)", Run: runNotImplemented}
|
||||
setEnabled := &Node{Word: "enabled", Help: "enable backend (not implemented)", Run: runNotImplemented}
|
||||
setDisabled := &Node{Word: "disable", Help: "disable backend (stop probing, remove from rotation)", Run: runDisableBackend}
|
||||
setEnabled := &Node{Word: "enable", Help: "enable backend (resume probing)", Run: runEnableBackend}
|
||||
setBackendName := &Node{
|
||||
Word: "<name>",
|
||||
Help: "backend name",
|
||||
@@ -96,9 +97,75 @@ func buildTree() *Node {
|
||||
Help: "modify a backend",
|
||||
Children: []*Node{setBackendName},
|
||||
}
|
||||
set.Children = []*Node{setBackend}
|
||||
// set frontend <name> pool <pool> backend <name> weight <0-100>
|
||||
setWeightValue := &Node{
|
||||
Word: "<weight>",
|
||||
Help: "weight 0-100",
|
||||
Run: runSetFrontendPoolBackendWeight,
|
||||
}
|
||||
setFrontendPoolBackendWeight := &Node{Word: "weight", Help: "set backend weight in pool", Children: []*Node{setWeightValue}}
|
||||
setFrontendPoolBackendName := &Node{
|
||||
Word: "<backend>",
|
||||
Help: "backend name",
|
||||
Dynamic: dynBackends,
|
||||
Children: []*Node{setFrontendPoolBackendWeight},
|
||||
}
|
||||
setFrontendPoolBackend := &Node{Word: "backend", Help: "select a backend", Children: []*Node{setFrontendPoolBackendName}}
|
||||
setFrontendPoolName := &Node{
|
||||
Word: "<pool>",
|
||||
Help: "pool name",
|
||||
Children: []*Node{setFrontendPoolBackend},
|
||||
}
|
||||
setFrontendPool := &Node{Word: "pool", Help: "select a pool", Children: []*Node{setFrontendPoolName}}
|
||||
setFrontendName := &Node{
|
||||
Word: "<name>",
|
||||
Help: "frontend name",
|
||||
Dynamic: dynFrontends,
|
||||
Children: []*Node{setFrontendPool},
|
||||
}
|
||||
setFrontend := &Node{
|
||||
Word: "frontend",
|
||||
Help: "modify a frontend",
|
||||
Children: []*Node{setFrontendName},
|
||||
}
|
||||
|
||||
root.Children = []*Node{show, set, quit, exit}
|
||||
set.Children = []*Node{setBackend, setFrontend}
|
||||
|
||||
// watch events [num <n>] [log [level <level>]] [backend] [frontend]
|
||||
//
|
||||
// All tokens after 'events' are captured as args via a self-referencing slot
|
||||
// node. This lets runWatchEvents parse the optional flags manually while still
|
||||
// providing tab-completion through the dynamic enumerator.
|
||||
var watchEventsOptSlot *Node
|
||||
watchEventsOptSlot = &Node{
|
||||
Word: "<opt>",
|
||||
Help: "watch option",
|
||||
Dynamic: dynWatchEventOpts,
|
||||
Run: runWatchEvents,
|
||||
}
|
||||
watchEventsOptSlot.Children = []*Node{watchEventsOptSlot}
|
||||
|
||||
watchEvents := &Node{
|
||||
Word: "events",
|
||||
Help: "stream events (press any key or Ctrl-C to stop)",
|
||||
Run: runWatchEvents,
|
||||
Children: []*Node{watchEventsOptSlot},
|
||||
}
|
||||
watch := &Node{
|
||||
Word: "watch",
|
||||
Help: "watch live event streams",
|
||||
Children: []*Node{watchEvents},
|
||||
}
|
||||
|
||||
// config check
|
||||
configCheck := &Node{Word: "check", Help: "check configuration file", Run: runConfigCheck}
|
||||
configNode := &Node{
|
||||
Word: "config",
|
||||
Help: "configuration commands",
|
||||
Children: []*Node{configCheck},
|
||||
}
|
||||
|
||||
root.Children = []*Node{show, set, watch, configNode, quit, exit}
|
||||
return root
|
||||
}
|
||||
|
||||
@@ -332,7 +399,7 @@ func runPauseBackend(ctx context.Context, client grpcapi.MaglevClient, args []st
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(ctx, callTimeout)
|
||||
defer cancel()
|
||||
info, err := client.PauseBackend(ctx, &grpcapi.PauseResumeRequest{Name: args[0]})
|
||||
info, err := client.PauseBackend(ctx, &grpcapi.BackendRequest{Name: args[0]})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -346,7 +413,7 @@ func runResumeBackend(ctx context.Context, client grpcapi.MaglevClient, args []s
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(ctx, callTimeout)
|
||||
defer cancel()
|
||||
info, err := client.ResumeBackend(ctx, &grpcapi.PauseResumeRequest{Name: args[0]})
|
||||
info, err := client.ResumeBackend(ctx, &grpcapi.BackendRequest{Name: args[0]})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -354,11 +421,86 @@ func runResumeBackend(ctx context.Context, client grpcapi.MaglevClient, args []s
|
||||
return nil
|
||||
}
|
||||
|
||||
func runNotImplemented(_ context.Context, _ grpcapi.MaglevClient, _ []string) error {
|
||||
fmt.Println("not implemented yet")
|
||||
func runSetFrontendPoolBackendWeight(ctx context.Context, client grpcapi.MaglevClient, args []string) error {
|
||||
if len(args) != 4 {
|
||||
return fmt.Errorf("usage: set frontend <name> pool <pool> backend <name> weight <0-100>")
|
||||
}
|
||||
frontendName, poolName, backendName, weightStr := args[0], args[1], args[2], args[3]
|
||||
weight, err := strconv.Atoi(weightStr)
|
||||
if err != nil || weight < 0 || weight > 100 {
|
||||
return fmt.Errorf("weight: expected integer 0-100, got %q", weightStr)
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(ctx, callTimeout)
|
||||
defer cancel()
|
||||
info, err := client.SetFrontendPoolBackendWeight(ctx, &grpcapi.SetWeightRequest{
|
||||
Frontend: frontendName,
|
||||
Pool: poolName,
|
||||
Backend: backendName,
|
||||
Weight: int32(weight),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Print the updated pool so the user can confirm the new weight.
|
||||
for _, pool := range info.Pools {
|
||||
if pool.Name != poolName {
|
||||
continue
|
||||
}
|
||||
for _, pb := range pool.Backends {
|
||||
if pb.Name == backendName {
|
||||
fmt.Printf("%s pool %s backend %s: weight set to %d\n", info.Name, pool.Name, pb.Name, pb.Weight)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func runEnableBackend(ctx context.Context, client grpcapi.MaglevClient, args []string) error {
|
||||
if len(args) == 0 {
|
||||
return fmt.Errorf("usage: set backend <name> enable")
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(ctx, callTimeout)
|
||||
defer cancel()
|
||||
info, err := client.EnableBackend(ctx, &grpcapi.BackendRequest{Name: args[0]})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("%s: enabled, state is '%s'\n", info.Name, info.State)
|
||||
return nil
|
||||
}
|
||||
|
||||
func runDisableBackend(ctx context.Context, client grpcapi.MaglevClient, args []string) error {
|
||||
if len(args) == 0 {
|
||||
return fmt.Errorf("usage: set backend <name> disable")
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(ctx, callTimeout)
|
||||
defer cancel()
|
||||
info, err := client.DisableBackend(ctx, &grpcapi.BackendRequest{Name: args[0]})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("%s: disabled, state is '%s'\n", info.Name, info.State)
|
||||
return nil
|
||||
}
|
||||
|
||||
func runConfigCheck(ctx context.Context, client grpcapi.MaglevClient, _ []string) error {
|
||||
ctx, cancel := context.WithTimeout(ctx, callTimeout)
|
||||
defer cancel()
|
||||
resp, err := client.CheckConfig(ctx, &grpcapi.CheckConfigRequest{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.Ok {
|
||||
fmt.Println("config ok")
|
||||
return nil
|
||||
}
|
||||
if resp.ParseError != "" {
|
||||
return fmt.Errorf("parse error: %s", resp.ParseError)
|
||||
}
|
||||
return fmt.Errorf("semantic error: %s", resp.SemanticError)
|
||||
}
|
||||
|
||||
// formatDuration formats a duration as Xd Xh Xm Xs without milliseconds.
|
||||
func formatDuration(d time.Duration) string {
|
||||
if d < 0 {
|
||||
|
||||
Reference in New Issue
Block a user