From 3227263d684f0395342d116ba0426fbabc81cb93 Mon Sep 17 00:00:00 2001 From: Pim van Pelt Date: Sat, 11 Apr 2026 22:03:22 +0200 Subject: [PATCH] Add GoVPP integration and GetVPPInfo gRPC call MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit VPP client (internal/vpp/) - New package managing connections to both VPP API and stats sockets, treated as a unit: if either drops, both are torn down and re-established together. - Run() loop: connect, fetch version via vpe.ShowVersion, read /sys/boottime from the stats segment, log vpp-connect, then monitor with control_ping every 10s. On failure, disconnect both, retry after 5s. - Registers as client name "vpp-maglev" (visible in VPP's "show api clients"). - Flags: --vpp-api-addr (default /run/vpp/api.sock) and --vpp-stats-addr (default /run/vpp/stats.sock). Empty api addr disables VPP integration entirely. gRPC / proto - Add GetVPPInfo RPC returning VPPInfo: version, build_date, build_directory, pid, boottime_ns, connecttime_ns. Both times are unix timestamps in nanoseconds — the client computes durations locally for display. - Returns codes.Unavailable if VPP is disabled or not connected. maglevc - Add 'show vpp info' command displaying version, build-date, build-dir, vpp-pid, vpp-boottime (with duration), and connected time (with duration). --- cmd/maglevc/commands.go | 34 +++ cmd/maglevc/tree_test.go | 7 +- cmd/maglevd/main.go | 12 +- docs/user-guide.md | 6 + go.mod | 8 +- go.sum | 39 ++- internal/grpcapi/maglev.pb.go | 350 ++++++++++++++++++-------- internal/grpcapi/maglev_grpc.pb.go | 38 +++ internal/grpcapi/server.go | 35 ++- internal/grpcapi/server_test.go | 2 +- internal/vpp/client.go | 265 +++++++++++++++++++ proto/maglev.proto | 12 + tests/01-maglevd/maglevd-lab/start.sh | 2 +- 13 files changed, 690 insertions(+), 120 deletions(-) create mode 100644 internal/vpp/client.go diff --git a/cmd/maglevc/commands.go b/cmd/maglevc/commands.go index 4e6fcd8..d892803 100644 --- a/cmd/maglevc/commands.go +++ b/cmd/maglevc/commands.go @@ -71,11 +71,20 @@ func buildTree() *Node { Children: []*Node{showHealthCheckName}, } + // show vpp info + showVPPInfo := &Node{Word: "info", Help: "Show VPP version, uptime, and connection status", Run: runShowVPPInfo} + showVPP := &Node{ + Word: "vpp", + Help: "VPP dataplane information", + Children: []*Node{showVPPInfo}, + } + show.Children = []*Node{ showVersion, showFrontends, showBackends, showHealthChecks, + showVPP, } // set backend pause|resume|disabled|enabled @@ -201,6 +210,31 @@ func dynNone(_ context.Context, _ grpcapi.MaglevClient) []string { return nil } // ---- run functions --------------------------------------------------------- +func runShowVPPInfo(ctx context.Context, client grpcapi.MaglevClient, _ []string) error { + ctx, cancel := context.WithTimeout(ctx, callTimeout) + defer cancel() + info, err := client.GetVPPInfo(ctx, &grpcapi.GetVPPInfoRequest{}) + if err != nil { + return err + } + w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) + fmt.Fprintf(w, "%s\t%s\n", label("version"), info.Version) + fmt.Fprintf(w, "%s\t%s\n", label("build-date"), info.BuildDate) + fmt.Fprintf(w, "%s\t%s\n", label("build-dir"), info.BuildDirectory) + fmt.Fprintf(w, "%s\t%d\n", label("vpp-pid"), info.Pid) + if info.BoottimeNs > 0 { + bootTime := time.Unix(0, info.BoottimeNs) + fmt.Fprintf(w, "%s\t%s (%s)\n", label("vpp-boottime"), + bootTime.Format("2006-01-02 15:04:05"), + formatDuration(time.Since(bootTime))) + } + connTime := time.Unix(0, info.ConnecttimeNs) + fmt.Fprintf(w, "%s\t%s (%s)\n", label("connected"), + connTime.Format("2006-01-02 15:04:05"), + formatDuration(time.Since(connTime))) + return w.Flush() +} + func runShowVersion(_ context.Context, _ grpcapi.MaglevClient, _ []string) error { fmt.Printf("maglevc %s (commit %s, built %s)\n", buildinfo.Version(), buildinfo.Commit(), buildinfo.Date()) diff --git a/cmd/maglevc/tree_test.go b/cmd/maglevc/tree_test.go index 9e9e90a..86431ea 100644 --- a/cmd/maglevc/tree_test.go +++ b/cmd/maglevc/tree_test.go @@ -28,6 +28,7 @@ func TestExpandPathsRoot(t *testing.T) { "watch events", "watch events ", "config check", + "show vpp info", "config reload", "quit", "exit", @@ -59,9 +60,9 @@ func TestExpandPathsShow(t *testing.T) { } } // version, frontends, frontends , backends, backends , - // healthchecks, healthchecks = 7 lines - if len(lines) != 7 { - t.Errorf("expected exactly 7 show subcommands, got %d", len(lines)) + // healthchecks, healthchecks , vpp info = 8 lines + if len(lines) != 8 { + t.Errorf("expected exactly 8 show subcommands, got %d", len(lines)) } } diff --git a/cmd/maglevd/main.go b/cmd/maglevd/main.go index 9adf84a..474c3f8 100644 --- a/cmd/maglevd/main.go +++ b/cmd/maglevd/main.go @@ -23,6 +23,7 @@ import ( "git.ipng.ch/ipng/vpp-maglev/internal/config" "git.ipng.ch/ipng/vpp-maglev/internal/grpcapi" "git.ipng.ch/ipng/vpp-maglev/internal/metrics" + "git.ipng.ch/ipng/vpp-maglev/internal/vpp" ) func main() { @@ -40,6 +41,8 @@ func run() error { configPath := stringFlag("config", "/etc/vpp-maglev/maglev.yaml", "MAGLEV_CONFIG", "path to maglev.yaml") grpcAddr := stringFlag("grpc-addr", ":9090", "MAGLEV_GRPC_ADDR", "gRPC listen address") metricsAddr := stringFlag("metrics-addr", ":9091", "MAGLEV_METRICS_ADDR", "Prometheus /metrics listen address (empty to disable)") + vppAPIAddr := stringFlag("vpp-api-addr", "/run/vpp/api.sock", "MAGLEV_VPP_API_ADDR", "VPP binary API socket path (empty to disable)") + vppStatsAddr := stringFlag("vpp-stats-addr", "/run/vpp/stats.sock", "MAGLEV_VPP_STATS_ADDR", "VPP stats socket path") logLevel := stringFlag("log-level", "info", "MAGLEV_LOG_LEVEL", "log level (debug|info|warn|error)") flag.Parse() @@ -92,13 +95,20 @@ func run() error { } }() + // ---- VPP connection ----------------------------------------------------- + var vppClient *vpp.Client + if *vppAPIAddr != "" { + vppClient = vpp.New(*vppAPIAddr, *vppStatsAddr) + go vppClient.Run(ctx) + } + // ---- gRPC server -------------------------------------------------------- lis, err := net.Listen("tcp", *grpcAddr) if err != nil { return fmt.Errorf("listen %s: %w", *grpcAddr, err) } srv := grpc.NewServer() - maglevServer := grpcapi.NewServer(ctx, chkr, logBroadcaster, *configPath) + maglevServer := grpcapi.NewServer(ctx, chkr, logBroadcaster, *configPath, vppClient) grpcapi.RegisterMaglevServer(srv, maglevServer) if *enableReflection { reflection.Register(srv) diff --git a/docs/user-guide.md b/docs/user-guide.md index 677d812..9335569 100644 --- a/docs/user-guide.md +++ b/docs/user-guide.md @@ -13,6 +13,8 @@ inspection and control. | `--config` | `MAGLEV_CONFIG` | `/etc/vpp-maglev/maglev.yaml` | Path to the YAML configuration file. | | `--grpc-addr` | `MAGLEV_GRPC_ADDR` | `:9090` | TCP address on which the gRPC server listens. | | `--metrics-addr` | `MAGLEV_METRICS_ADDR` | `:9091` | TCP address for the Prometheus `/metrics` HTTP endpoint. Set to empty to disable. | +| `--vpp-api-addr` | `MAGLEV_VPP_API_ADDR` | `/run/vpp/api.sock` | VPP binary API socket path. Set to empty to disable VPP integration. | +| `--vpp-stats-addr` | `MAGLEV_VPP_STATS_ADDR` | `/run/vpp/stats.sock` | VPP stats socket path. | | `--log-level` | `MAGLEV_LOG_LEVEL` | `info` | Log verbosity: `debug`, `info`, `warn`, or `error`. | | `--check` | — | — | Read and validate the config file, then exit. Exits 0 if the config is valid, 1 on YAML parse error, 2 on semantic error. | | `--reflection` | — | `true` | Enable gRPC server reflection. Allows `grpcurl` to introspect the API without the `.proto` file. Set to `false` to disable. | @@ -83,6 +85,10 @@ show backends [] Without name: list all backend names. show healthchecks [] Without name: list all health-check names. With name: show full health-check configuration. +show vpp info Show VPP version, build date, PID, uptime, and when + maglevd connected. Returns an error if VPP is not + connected. + set backend pause Suspend health checking for a backend, freezing its state. set backend resume Resume health checking; backend re-enters unknown state and is probed immediately. diff --git a/go.mod b/go.mod index 2e364c1..66b89d7 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,9 @@ go 1.25.0 require ( github.com/chzyer/readline v1.5.1 + github.com/prometheus/client_golang v1.23.2 github.com/vishvananda/netns v0.0.5 + go.fd.io/govpp v0.12.0 golang.org/x/net v0.52.0 golang.org/x/sys v0.43.0 google.golang.org/grpc v1.80.0 @@ -15,11 +17,15 @@ require ( require ( github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/fsnotify/fsnotify v1.9.0 // indirect + github.com/ftrvxmtrx/fd v0.0.0-20150925145434-c6d800382fff // indirect + github.com/kr/text v0.2.0 // indirect + github.com/lunixbochs/struc v0.0.0-20200521075829-a4cb8d33dbbe // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/prometheus/client_golang v1.23.2 // indirect github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/common v0.66.1 // indirect github.com/prometheus/procfs v0.16.1 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect go.yaml.in/yaml/v2 v2.4.2 // indirect golang.org/x/text v0.35.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260120221211-b8f7ae30c516 // indirect diff --git a/go.sum b/go.sum index 2b4510a..a2dbf33 100644 --- a/go.sum +++ b/go.sum @@ -8,6 +8,14 @@ github.com/chzyer/readline v1.5.1 h1:upd/6fQk4src78LMRzh5vItIt361/o4uq553V8B5sGI github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk= github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04= github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/ftrvxmtrx/fd v0.0.0-20150925145434-c6d800382fff h1:zk1wwii7uXmI0znwU+lqg+wFL9G5+vm5I+9rv2let60= +github.com/ftrvxmtrx/fd v0.0.0-20150925145434-c6d800382fff/go.mod h1:yUhRXHewUVJ1k89wHKP68xfzk7kwXUx/DV1nx4EBMbw= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= @@ -18,8 +26,22 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/lunixbochs/struc v0.0.0-20200521075829-a4cb8d33dbbe h1:ewr1srjRCmcQogPQ/NCx6XCk6LGVmsVCc9Y3vvPZj+Y= +github.com/lunixbochs/struc v0.0.0-20200521075829-a4cb8d33dbbe/go.mod h1:vy1vK6wD6j7xX6O6hXe621WabdtNkou2h7uRtTfRMyg= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/onsi/gomega v1.37.0 h1:CdEG8g0S133B4OswTDC/5XPSzE1OeP29QOioj2PID2Y= +github.com/onsi/gomega v1.37.0/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= @@ -28,8 +50,18 @@ github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9Z github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA= github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY= github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= +go.fd.io/govpp v0.12.0 h1:5HnMzsKHSFdxglsFyEhR0g+CzncWiLYXG2NDYgNUrnE= +go.fd.io/govpp v0.12.0/go.mod h1:6qp4J/+jumgXXoowrtVAk13PSXS6+ghPrDG8CyuU/Is= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= @@ -42,11 +74,14 @@ go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2W go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0= golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8= @@ -59,7 +94,9 @@ google.golang.org/grpc v1.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM= google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/grpcapi/maglev.pb.go b/internal/grpcapi/maglev.pb.go index 205d680..c6da847 100644 --- a/internal/grpcapi/maglev.pb.go +++ b/internal/grpcapi/maglev.pb.go @@ -505,6 +505,126 @@ func (x *ReloadConfigResponse) GetReloadError() string { return "" } +type GetVPPInfoRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetVPPInfoRequest) Reset() { + *x = GetVPPInfoRequest{} + mi := &file_proto_maglev_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetVPPInfoRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetVPPInfoRequest) ProtoMessage() {} + +func (x *GetVPPInfoRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_maglev_proto_msgTypes[11] + 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 GetVPPInfoRequest.ProtoReflect.Descriptor instead. +func (*GetVPPInfoRequest) Descriptor() ([]byte, []int) { + return file_proto_maglev_proto_rawDescGZIP(), []int{11} +} + +type VPPInfo struct { + state protoimpl.MessageState `protogen:"open.v1"` + Version string `protobuf:"bytes,1,opt,name=version,proto3" json:"version,omitempty"` + BuildDate string `protobuf:"bytes,2,opt,name=build_date,json=buildDate,proto3" json:"build_date,omitempty"` + BuildDirectory string `protobuf:"bytes,3,opt,name=build_directory,json=buildDirectory,proto3" json:"build_directory,omitempty"` + Pid uint32 `protobuf:"varint,4,opt,name=pid,proto3" json:"pid,omitempty"` + BoottimeNs int64 `protobuf:"varint,5,opt,name=boottime_ns,json=boottimeNs,proto3" json:"boottime_ns,omitempty"` // unix timestamp (ns) when VPP started (from /sys/boottime) + ConnecttimeNs int64 `protobuf:"varint,6,opt,name=connecttime_ns,json=connecttimeNs,proto3" json:"connecttime_ns,omitempty"` // unix timestamp (ns) when maglevd connected to VPP + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *VPPInfo) Reset() { + *x = VPPInfo{} + mi := &file_proto_maglev_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *VPPInfo) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*VPPInfo) ProtoMessage() {} + +func (x *VPPInfo) ProtoReflect() protoreflect.Message { + mi := &file_proto_maglev_proto_msgTypes[12] + 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 VPPInfo.ProtoReflect.Descriptor instead. +func (*VPPInfo) Descriptor() ([]byte, []int) { + return file_proto_maglev_proto_rawDescGZIP(), []int{12} +} + +func (x *VPPInfo) GetVersion() string { + if x != nil { + return x.Version + } + return "" +} + +func (x *VPPInfo) GetBuildDate() string { + if x != nil { + return x.BuildDate + } + return "" +} + +func (x *VPPInfo) GetBuildDirectory() string { + if x != nil { + return x.BuildDirectory + } + return "" +} + +func (x *VPPInfo) GetPid() uint32 { + if x != nil { + return x.Pid + } + return 0 +} + +func (x *VPPInfo) GetBoottimeNs() int64 { + if x != nil { + return x.BoottimeNs + } + return 0 +} + +func (x *VPPInfo) GetConnecttimeNs() int64 { + if x != nil { + return x.ConnecttimeNs + } + return 0 +} + type SetWeightRequest struct { state protoimpl.MessageState `protogen:"open.v1"` Frontend string `protobuf:"bytes,1,opt,name=frontend,proto3" json:"frontend,omitempty"` @@ -517,7 +637,7 @@ type SetWeightRequest struct { func (x *SetWeightRequest) Reset() { *x = SetWeightRequest{} - mi := &file_proto_maglev_proto_msgTypes[11] + mi := &file_proto_maglev_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -529,7 +649,7 @@ func (x *SetWeightRequest) String() string { func (*SetWeightRequest) ProtoMessage() {} func (x *SetWeightRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_maglev_proto_msgTypes[11] + mi := &file_proto_maglev_proto_msgTypes[13] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -542,7 +662,7 @@ func (x *SetWeightRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SetWeightRequest.ProtoReflect.Descriptor instead. func (*SetWeightRequest) Descriptor() ([]byte, []int) { - return file_proto_maglev_proto_rawDescGZIP(), []int{11} + return file_proto_maglev_proto_rawDescGZIP(), []int{13} } func (x *SetWeightRequest) GetFrontend() string { @@ -587,7 +707,7 @@ type WatchRequest struct { func (x *WatchRequest) Reset() { *x = WatchRequest{} - mi := &file_proto_maglev_proto_msgTypes[12] + mi := &file_proto_maglev_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -599,7 +719,7 @@ func (x *WatchRequest) String() string { func (*WatchRequest) ProtoMessage() {} func (x *WatchRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_maglev_proto_msgTypes[12] + mi := &file_proto_maglev_proto_msgTypes[14] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -612,7 +732,7 @@ func (x *WatchRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use WatchRequest.ProtoReflect.Descriptor instead. func (*WatchRequest) Descriptor() ([]byte, []int) { - return file_proto_maglev_proto_rawDescGZIP(), []int{12} + return file_proto_maglev_proto_rawDescGZIP(), []int{14} } func (x *WatchRequest) GetLog() bool { @@ -652,7 +772,7 @@ type ListFrontendsResponse struct { func (x *ListFrontendsResponse) Reset() { *x = ListFrontendsResponse{} - mi := &file_proto_maglev_proto_msgTypes[13] + mi := &file_proto_maglev_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -664,7 +784,7 @@ func (x *ListFrontendsResponse) String() string { func (*ListFrontendsResponse) ProtoMessage() {} func (x *ListFrontendsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_maglev_proto_msgTypes[13] + mi := &file_proto_maglev_proto_msgTypes[15] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -677,7 +797,7 @@ func (x *ListFrontendsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListFrontendsResponse.ProtoReflect.Descriptor instead. func (*ListFrontendsResponse) Descriptor() ([]byte, []int) { - return file_proto_maglev_proto_rawDescGZIP(), []int{13} + return file_proto_maglev_proto_rawDescGZIP(), []int{15} } func (x *ListFrontendsResponse) GetFrontendNames() []string { @@ -697,7 +817,7 @@ type PoolBackendInfo struct { func (x *PoolBackendInfo) Reset() { *x = PoolBackendInfo{} - mi := &file_proto_maglev_proto_msgTypes[14] + mi := &file_proto_maglev_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -709,7 +829,7 @@ func (x *PoolBackendInfo) String() string { func (*PoolBackendInfo) ProtoMessage() {} func (x *PoolBackendInfo) ProtoReflect() protoreflect.Message { - mi := &file_proto_maglev_proto_msgTypes[14] + mi := &file_proto_maglev_proto_msgTypes[16] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -722,7 +842,7 @@ func (x *PoolBackendInfo) ProtoReflect() protoreflect.Message { // Deprecated: Use PoolBackendInfo.ProtoReflect.Descriptor instead. func (*PoolBackendInfo) Descriptor() ([]byte, []int) { - return file_proto_maglev_proto_rawDescGZIP(), []int{14} + return file_proto_maglev_proto_rawDescGZIP(), []int{16} } func (x *PoolBackendInfo) GetName() string { @@ -749,7 +869,7 @@ type PoolInfo struct { func (x *PoolInfo) Reset() { *x = PoolInfo{} - mi := &file_proto_maglev_proto_msgTypes[15] + mi := &file_proto_maglev_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -761,7 +881,7 @@ func (x *PoolInfo) String() string { func (*PoolInfo) ProtoMessage() {} func (x *PoolInfo) ProtoReflect() protoreflect.Message { - mi := &file_proto_maglev_proto_msgTypes[15] + mi := &file_proto_maglev_proto_msgTypes[17] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -774,7 +894,7 @@ func (x *PoolInfo) ProtoReflect() protoreflect.Message { // Deprecated: Use PoolInfo.ProtoReflect.Descriptor instead. func (*PoolInfo) Descriptor() ([]byte, []int) { - return file_proto_maglev_proto_rawDescGZIP(), []int{15} + return file_proto_maglev_proto_rawDescGZIP(), []int{17} } func (x *PoolInfo) GetName() string { @@ -805,7 +925,7 @@ type FrontendInfo struct { func (x *FrontendInfo) Reset() { *x = FrontendInfo{} - mi := &file_proto_maglev_proto_msgTypes[16] + mi := &file_proto_maglev_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -817,7 +937,7 @@ func (x *FrontendInfo) String() string { func (*FrontendInfo) ProtoMessage() {} func (x *FrontendInfo) ProtoReflect() protoreflect.Message { - mi := &file_proto_maglev_proto_msgTypes[16] + mi := &file_proto_maglev_proto_msgTypes[18] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -830,7 +950,7 @@ func (x *FrontendInfo) ProtoReflect() protoreflect.Message { // Deprecated: Use FrontendInfo.ProtoReflect.Descriptor instead. func (*FrontendInfo) Descriptor() ([]byte, []int) { - return file_proto_maglev_proto_rawDescGZIP(), []int{16} + return file_proto_maglev_proto_rawDescGZIP(), []int{18} } func (x *FrontendInfo) GetName() string { @@ -884,7 +1004,7 @@ type ListBackendsResponse struct { func (x *ListBackendsResponse) Reset() { *x = ListBackendsResponse{} - mi := &file_proto_maglev_proto_msgTypes[17] + mi := &file_proto_maglev_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -896,7 +1016,7 @@ func (x *ListBackendsResponse) String() string { func (*ListBackendsResponse) ProtoMessage() {} func (x *ListBackendsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_maglev_proto_msgTypes[17] + mi := &file_proto_maglev_proto_msgTypes[19] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -909,7 +1029,7 @@ func (x *ListBackendsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListBackendsResponse.ProtoReflect.Descriptor instead. func (*ListBackendsResponse) Descriptor() ([]byte, []int) { - return file_proto_maglev_proto_rawDescGZIP(), []int{17} + return file_proto_maglev_proto_rawDescGZIP(), []int{19} } func (x *ListBackendsResponse) GetBackendNames() []string { @@ -928,7 +1048,7 @@ type ListHealthChecksResponse struct { func (x *ListHealthChecksResponse) Reset() { *x = ListHealthChecksResponse{} - mi := &file_proto_maglev_proto_msgTypes[18] + mi := &file_proto_maglev_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -940,7 +1060,7 @@ func (x *ListHealthChecksResponse) String() string { func (*ListHealthChecksResponse) ProtoMessage() {} func (x *ListHealthChecksResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_maglev_proto_msgTypes[18] + mi := &file_proto_maglev_proto_msgTypes[20] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -953,7 +1073,7 @@ func (x *ListHealthChecksResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListHealthChecksResponse.ProtoReflect.Descriptor instead. func (*ListHealthChecksResponse) Descriptor() ([]byte, []int) { - return file_proto_maglev_proto_rawDescGZIP(), []int{18} + return file_proto_maglev_proto_rawDescGZIP(), []int{20} } func (x *ListHealthChecksResponse) GetNames() []string { @@ -978,7 +1098,7 @@ type HTTPCheckParams struct { func (x *HTTPCheckParams) Reset() { *x = HTTPCheckParams{} - mi := &file_proto_maglev_proto_msgTypes[19] + mi := &file_proto_maglev_proto_msgTypes[21] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -990,7 +1110,7 @@ func (x *HTTPCheckParams) String() string { func (*HTTPCheckParams) ProtoMessage() {} func (x *HTTPCheckParams) ProtoReflect() protoreflect.Message { - mi := &file_proto_maglev_proto_msgTypes[19] + mi := &file_proto_maglev_proto_msgTypes[21] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1003,7 +1123,7 @@ func (x *HTTPCheckParams) ProtoReflect() protoreflect.Message { // Deprecated: Use HTTPCheckParams.ProtoReflect.Descriptor instead. func (*HTTPCheckParams) Descriptor() ([]byte, []int) { - return file_proto_maglev_proto_rawDescGZIP(), []int{19} + return file_proto_maglev_proto_rawDescGZIP(), []int{21} } func (x *HTTPCheckParams) GetPath() string { @@ -1066,7 +1186,7 @@ type TCPCheckParams struct { func (x *TCPCheckParams) Reset() { *x = TCPCheckParams{} - mi := &file_proto_maglev_proto_msgTypes[20] + mi := &file_proto_maglev_proto_msgTypes[22] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1078,7 +1198,7 @@ func (x *TCPCheckParams) String() string { func (*TCPCheckParams) ProtoMessage() {} func (x *TCPCheckParams) ProtoReflect() protoreflect.Message { - mi := &file_proto_maglev_proto_msgTypes[20] + mi := &file_proto_maglev_proto_msgTypes[22] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1091,7 +1211,7 @@ func (x *TCPCheckParams) ProtoReflect() protoreflect.Message { // Deprecated: Use TCPCheckParams.ProtoReflect.Descriptor instead. func (*TCPCheckParams) Descriptor() ([]byte, []int) { - return file_proto_maglev_proto_rawDescGZIP(), []int{20} + return file_proto_maglev_proto_rawDescGZIP(), []int{22} } func (x *TCPCheckParams) GetSsl() bool { @@ -1136,7 +1256,7 @@ type HealthCheckInfo struct { func (x *HealthCheckInfo) Reset() { *x = HealthCheckInfo{} - mi := &file_proto_maglev_proto_msgTypes[21] + mi := &file_proto_maglev_proto_msgTypes[23] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1148,7 +1268,7 @@ func (x *HealthCheckInfo) String() string { func (*HealthCheckInfo) ProtoMessage() {} func (x *HealthCheckInfo) ProtoReflect() protoreflect.Message { - mi := &file_proto_maglev_proto_msgTypes[21] + mi := &file_proto_maglev_proto_msgTypes[23] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1161,7 +1281,7 @@ func (x *HealthCheckInfo) ProtoReflect() protoreflect.Message { // Deprecated: Use HealthCheckInfo.ProtoReflect.Descriptor instead. func (*HealthCheckInfo) Descriptor() ([]byte, []int) { - return file_proto_maglev_proto_rawDescGZIP(), []int{21} + return file_proto_maglev_proto_rawDescGZIP(), []int{23} } func (x *HealthCheckInfo) GetName() string { @@ -1269,7 +1389,7 @@ type BackendInfo struct { func (x *BackendInfo) Reset() { *x = BackendInfo{} - mi := &file_proto_maglev_proto_msgTypes[22] + mi := &file_proto_maglev_proto_msgTypes[24] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1281,7 +1401,7 @@ func (x *BackendInfo) String() string { func (*BackendInfo) ProtoMessage() {} func (x *BackendInfo) ProtoReflect() protoreflect.Message { - mi := &file_proto_maglev_proto_msgTypes[22] + mi := &file_proto_maglev_proto_msgTypes[24] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1294,7 +1414,7 @@ func (x *BackendInfo) ProtoReflect() protoreflect.Message { // Deprecated: Use BackendInfo.ProtoReflect.Descriptor instead. func (*BackendInfo) Descriptor() ([]byte, []int) { - return file_proto_maglev_proto_rawDescGZIP(), []int{22} + return file_proto_maglev_proto_rawDescGZIP(), []int{24} } func (x *BackendInfo) GetName() string { @@ -1350,7 +1470,7 @@ type TransitionRecord struct { func (x *TransitionRecord) Reset() { *x = TransitionRecord{} - mi := &file_proto_maglev_proto_msgTypes[23] + mi := &file_proto_maglev_proto_msgTypes[25] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1362,7 +1482,7 @@ func (x *TransitionRecord) String() string { func (*TransitionRecord) ProtoMessage() {} func (x *TransitionRecord) ProtoReflect() protoreflect.Message { - mi := &file_proto_maglev_proto_msgTypes[23] + mi := &file_proto_maglev_proto_msgTypes[25] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1375,7 +1495,7 @@ func (x *TransitionRecord) ProtoReflect() protoreflect.Message { // Deprecated: Use TransitionRecord.ProtoReflect.Descriptor instead. func (*TransitionRecord) Descriptor() ([]byte, []int) { - return file_proto_maglev_proto_rawDescGZIP(), []int{23} + return file_proto_maglev_proto_rawDescGZIP(), []int{25} } func (x *TransitionRecord) GetFrom() string { @@ -1410,7 +1530,7 @@ type LogAttr struct { func (x *LogAttr) Reset() { *x = LogAttr{} - mi := &file_proto_maglev_proto_msgTypes[24] + mi := &file_proto_maglev_proto_msgTypes[26] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1422,7 +1542,7 @@ func (x *LogAttr) String() string { func (*LogAttr) ProtoMessage() {} func (x *LogAttr) ProtoReflect() protoreflect.Message { - mi := &file_proto_maglev_proto_msgTypes[24] + mi := &file_proto_maglev_proto_msgTypes[26] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1435,7 +1555,7 @@ func (x *LogAttr) ProtoReflect() protoreflect.Message { // Deprecated: Use LogAttr.ProtoReflect.Descriptor instead. func (*LogAttr) Descriptor() ([]byte, []int) { - return file_proto_maglev_proto_rawDescGZIP(), []int{24} + return file_proto_maglev_proto_rawDescGZIP(), []int{26} } func (x *LogAttr) GetKey() string { @@ -1465,7 +1585,7 @@ type LogEvent struct { func (x *LogEvent) Reset() { *x = LogEvent{} - mi := &file_proto_maglev_proto_msgTypes[25] + mi := &file_proto_maglev_proto_msgTypes[27] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1477,7 +1597,7 @@ func (x *LogEvent) String() string { func (*LogEvent) ProtoMessage() {} func (x *LogEvent) ProtoReflect() protoreflect.Message { - mi := &file_proto_maglev_proto_msgTypes[25] + mi := &file_proto_maglev_proto_msgTypes[27] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1490,7 +1610,7 @@ func (x *LogEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use LogEvent.ProtoReflect.Descriptor instead. func (*LogEvent) Descriptor() ([]byte, []int) { - return file_proto_maglev_proto_rawDescGZIP(), []int{25} + return file_proto_maglev_proto_rawDescGZIP(), []int{27} } func (x *LogEvent) GetAtUnixNs() int64 { @@ -1532,7 +1652,7 @@ type BackendEvent struct { func (x *BackendEvent) Reset() { *x = BackendEvent{} - mi := &file_proto_maglev_proto_msgTypes[26] + mi := &file_proto_maglev_proto_msgTypes[28] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1544,7 +1664,7 @@ func (x *BackendEvent) String() string { func (*BackendEvent) ProtoMessage() {} func (x *BackendEvent) ProtoReflect() protoreflect.Message { - mi := &file_proto_maglev_proto_msgTypes[26] + mi := &file_proto_maglev_proto_msgTypes[28] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1557,7 +1677,7 @@ func (x *BackendEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use BackendEvent.ProtoReflect.Descriptor instead. func (*BackendEvent) Descriptor() ([]byte, []int) { - return file_proto_maglev_proto_rawDescGZIP(), []int{26} + return file_proto_maglev_proto_rawDescGZIP(), []int{28} } func (x *BackendEvent) GetBackendName() string { @@ -1583,7 +1703,7 @@ type FrontendEvent struct { func (x *FrontendEvent) Reset() { *x = FrontendEvent{} - mi := &file_proto_maglev_proto_msgTypes[27] + mi := &file_proto_maglev_proto_msgTypes[29] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1595,7 +1715,7 @@ func (x *FrontendEvent) String() string { func (*FrontendEvent) ProtoMessage() {} func (x *FrontendEvent) ProtoReflect() protoreflect.Message { - mi := &file_proto_maglev_proto_msgTypes[27] + mi := &file_proto_maglev_proto_msgTypes[29] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1608,7 +1728,7 @@ func (x *FrontendEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use FrontendEvent.ProtoReflect.Descriptor instead. func (*FrontendEvent) Descriptor() ([]byte, []int) { - return file_proto_maglev_proto_rawDescGZIP(), []int{27} + return file_proto_maglev_proto_rawDescGZIP(), []int{29} } // Event is the envelope returned by WatchEvents. @@ -1626,7 +1746,7 @@ type Event struct { func (x *Event) Reset() { *x = Event{} - mi := &file_proto_maglev_proto_msgTypes[28] + mi := &file_proto_maglev_proto_msgTypes[30] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1638,7 +1758,7 @@ func (x *Event) String() string { func (*Event) ProtoMessage() {} func (x *Event) ProtoReflect() protoreflect.Message { - mi := &file_proto_maglev_proto_msgTypes[28] + mi := &file_proto_maglev_proto_msgTypes[30] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1651,7 +1771,7 @@ func (x *Event) ProtoReflect() protoreflect.Message { // Deprecated: Use Event.ProtoReflect.Descriptor instead. func (*Event) Descriptor() ([]byte, []int) { - return file_proto_maglev_proto_rawDescGZIP(), []int{28} + return file_proto_maglev_proto_rawDescGZIP(), []int{30} } func (x *Event) GetEvent() isEvent_Event { @@ -1738,7 +1858,17 @@ const file_proto_maglev_proto_rawDesc = "" + "\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" + + "\freload_error\x18\x04 \x01(\tR\vreloadError\"\x13\n" + + "\x11GetVPPInfoRequest\"\xc5\x01\n" + + "\aVPPInfo\x12\x18\n" + + "\aversion\x18\x01 \x01(\tR\aversion\x12\x1d\n" + + "\n" + + "build_date\x18\x02 \x01(\tR\tbuildDate\x12'\n" + + "\x0fbuild_directory\x18\x03 \x01(\tR\x0ebuildDirectory\x12\x10\n" + + "\x03pid\x18\x04 \x01(\rR\x03pid\x12\x1f\n" + + "\vboottime_ns\x18\x05 \x01(\x03R\n" + + "boottimeNs\x12%\n" + + "\x0econnecttime_ns\x18\x06 \x01(\x03R\rconnecttimeNs\"t\n" + "\x10SetWeightRequest\x12\x1a\n" + "\bfrontend\x18\x01 \x01(\tR\bfrontend\x12\x12\n" + "\x04pool\x18\x02 \x01(\tR\x04pool\x12\x18\n" + @@ -1834,7 +1964,7 @@ const file_proto_maglev_proto_rawDesc = "" + "\x03log\x18\x01 \x01(\v2\x10.maglev.LogEventH\x00R\x03log\x120\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" + - "\x05event2\xd2\a\n" + + "\x05event2\x8c\b\n" + "\x06Maglev\x12L\n" + "\rListFrontends\x12\x1c.maglev.ListFrontendsRequest\x1a\x1d.maglev.ListFrontendsResponse\x12?\n" + "\vGetFrontend\x12\x1a.maglev.GetFrontendRequest\x1a\x14.maglev.FrontendInfo\x12I\n" + @@ -1850,7 +1980,9 @@ const file_proto_maglev_proto_rawDesc = "" + "\x1cSetFrontendPoolBackendWeight\x12\x18.maglev.SetWeightRequest\x1a\x14.maglev.FrontendInfo\x124\n" + "\vWatchEvents\x12\x14.maglev.WatchRequest\x1a\r.maglev.Event0\x01\x12F\n" + "\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" + "\fReloadConfig\x12\x1b.maglev.ReloadConfigRequest\x1a\x1c.maglev.ReloadConfigResponse\x128\n" + + "\n" + + "GetVPPInfo\x12\x19.maglev.GetVPPInfoRequest\x1a\x0f.maglev.VPPInfoB.Z,git.ipng.ch/ipng/vpp-maglev/internal/grpcapib\x06proto3" var ( file_proto_maglev_proto_rawDescOnce sync.Once @@ -1864,7 +1996,7 @@ func file_proto_maglev_proto_rawDescGZIP() []byte { return file_proto_maglev_proto_rawDescData } -var file_proto_maglev_proto_msgTypes = make([]protoimpl.MessageInfo, 29) +var file_proto_maglev_proto_msgTypes = make([]protoimpl.MessageInfo, 31) var file_proto_maglev_proto_goTypes = []any{ (*ListFrontendsRequest)(nil), // 0: maglev.ListFrontendsRequest (*GetFrontendRequest)(nil), // 1: maglev.GetFrontendRequest @@ -1877,36 +2009,38 @@ var file_proto_maglev_proto_goTypes = []any{ (*CheckConfigResponse)(nil), // 8: maglev.CheckConfigResponse (*ReloadConfigRequest)(nil), // 9: maglev.ReloadConfigRequest (*ReloadConfigResponse)(nil), // 10: maglev.ReloadConfigResponse - (*SetWeightRequest)(nil), // 11: maglev.SetWeightRequest - (*WatchRequest)(nil), // 12: maglev.WatchRequest - (*ListFrontendsResponse)(nil), // 13: maglev.ListFrontendsResponse - (*PoolBackendInfo)(nil), // 14: maglev.PoolBackendInfo - (*PoolInfo)(nil), // 15: maglev.PoolInfo - (*FrontendInfo)(nil), // 16: maglev.FrontendInfo - (*ListBackendsResponse)(nil), // 17: maglev.ListBackendsResponse - (*ListHealthChecksResponse)(nil), // 18: maglev.ListHealthChecksResponse - (*HTTPCheckParams)(nil), // 19: maglev.HTTPCheckParams - (*TCPCheckParams)(nil), // 20: maglev.TCPCheckParams - (*HealthCheckInfo)(nil), // 21: maglev.HealthCheckInfo - (*BackendInfo)(nil), // 22: maglev.BackendInfo - (*TransitionRecord)(nil), // 23: maglev.TransitionRecord - (*LogAttr)(nil), // 24: maglev.LogAttr - (*LogEvent)(nil), // 25: maglev.LogEvent - (*BackendEvent)(nil), // 26: maglev.BackendEvent - (*FrontendEvent)(nil), // 27: maglev.FrontendEvent - (*Event)(nil), // 28: maglev.Event + (*GetVPPInfoRequest)(nil), // 11: maglev.GetVPPInfoRequest + (*VPPInfo)(nil), // 12: maglev.VPPInfo + (*SetWeightRequest)(nil), // 13: maglev.SetWeightRequest + (*WatchRequest)(nil), // 14: maglev.WatchRequest + (*ListFrontendsResponse)(nil), // 15: maglev.ListFrontendsResponse + (*PoolBackendInfo)(nil), // 16: maglev.PoolBackendInfo + (*PoolInfo)(nil), // 17: maglev.PoolInfo + (*FrontendInfo)(nil), // 18: maglev.FrontendInfo + (*ListBackendsResponse)(nil), // 19: maglev.ListBackendsResponse + (*ListHealthChecksResponse)(nil), // 20: maglev.ListHealthChecksResponse + (*HTTPCheckParams)(nil), // 21: maglev.HTTPCheckParams + (*TCPCheckParams)(nil), // 22: maglev.TCPCheckParams + (*HealthCheckInfo)(nil), // 23: maglev.HealthCheckInfo + (*BackendInfo)(nil), // 24: maglev.BackendInfo + (*TransitionRecord)(nil), // 25: maglev.TransitionRecord + (*LogAttr)(nil), // 26: maglev.LogAttr + (*LogEvent)(nil), // 27: maglev.LogEvent + (*BackendEvent)(nil), // 28: maglev.BackendEvent + (*FrontendEvent)(nil), // 29: maglev.FrontendEvent + (*Event)(nil), // 30: maglev.Event } var file_proto_maglev_proto_depIdxs = []int32{ - 14, // 0: maglev.PoolInfo.backends:type_name -> maglev.PoolBackendInfo - 15, // 1: maglev.FrontendInfo.pools:type_name -> maglev.PoolInfo - 19, // 2: maglev.HealthCheckInfo.http:type_name -> maglev.HTTPCheckParams - 20, // 3: maglev.HealthCheckInfo.tcp:type_name -> maglev.TCPCheckParams - 23, // 4: maglev.BackendInfo.transitions:type_name -> maglev.TransitionRecord - 24, // 5: maglev.LogEvent.attrs:type_name -> maglev.LogAttr - 23, // 6: maglev.BackendEvent.transition:type_name -> maglev.TransitionRecord - 25, // 7: maglev.Event.log:type_name -> maglev.LogEvent - 26, // 8: maglev.Event.backend:type_name -> maglev.BackendEvent - 27, // 9: maglev.Event.frontend:type_name -> maglev.FrontendEvent + 16, // 0: maglev.PoolInfo.backends:type_name -> maglev.PoolBackendInfo + 17, // 1: maglev.FrontendInfo.pools:type_name -> maglev.PoolInfo + 21, // 2: maglev.HealthCheckInfo.http:type_name -> maglev.HTTPCheckParams + 22, // 3: maglev.HealthCheckInfo.tcp:type_name -> maglev.TCPCheckParams + 25, // 4: maglev.BackendInfo.transitions:type_name -> maglev.TransitionRecord + 26, // 5: maglev.LogEvent.attrs:type_name -> maglev.LogAttr + 25, // 6: maglev.BackendEvent.transition:type_name -> maglev.TransitionRecord + 27, // 7: maglev.Event.log:type_name -> maglev.LogEvent + 28, // 8: maglev.Event.backend:type_name -> maglev.BackendEvent + 29, // 9: maglev.Event.frontend:type_name -> maglev.FrontendEvent 0, // 10: maglev.Maglev.ListFrontends:input_type -> maglev.ListFrontendsRequest 1, // 11: maglev.Maglev.GetFrontend:input_type -> maglev.GetFrontendRequest 2, // 12: maglev.Maglev.ListBackends:input_type -> maglev.ListBackendsRequest @@ -1917,26 +2051,28 @@ var file_proto_maglev_proto_depIdxs = []int32{ 4, // 17: maglev.Maglev.DisableBackend:input_type -> maglev.BackendRequest 5, // 18: maglev.Maglev.ListHealthChecks:input_type -> maglev.ListHealthChecksRequest 6, // 19: maglev.Maglev.GetHealthCheck:input_type -> maglev.GetHealthCheckRequest - 11, // 20: maglev.Maglev.SetFrontendPoolBackendWeight:input_type -> maglev.SetWeightRequest - 12, // 21: maglev.Maglev.WatchEvents:input_type -> maglev.WatchRequest + 13, // 20: maglev.Maglev.SetFrontendPoolBackendWeight:input_type -> maglev.SetWeightRequest + 14, // 21: maglev.Maglev.WatchEvents:input_type -> maglev.WatchRequest 7, // 22: maglev.Maglev.CheckConfig:input_type -> maglev.CheckConfigRequest 9, // 23: maglev.Maglev.ReloadConfig:input_type -> maglev.ReloadConfigRequest - 13, // 24: maglev.Maglev.ListFrontends:output_type -> maglev.ListFrontendsResponse - 16, // 25: maglev.Maglev.GetFrontend:output_type -> maglev.FrontendInfo - 17, // 26: maglev.Maglev.ListBackends:output_type -> maglev.ListBackendsResponse - 22, // 27: maglev.Maglev.GetBackend:output_type -> maglev.BackendInfo - 22, // 28: maglev.Maglev.PauseBackend:output_type -> maglev.BackendInfo - 22, // 29: maglev.Maglev.ResumeBackend:output_type -> maglev.BackendInfo - 22, // 30: maglev.Maglev.EnableBackend:output_type -> maglev.BackendInfo - 22, // 31: maglev.Maglev.DisableBackend:output_type -> maglev.BackendInfo - 18, // 32: maglev.Maglev.ListHealthChecks:output_type -> maglev.ListHealthChecksResponse - 21, // 33: maglev.Maglev.GetHealthCheck:output_type -> maglev.HealthCheckInfo - 16, // 34: maglev.Maglev.SetFrontendPoolBackendWeight:output_type -> maglev.FrontendInfo - 28, // 35: maglev.Maglev.WatchEvents:output_type -> maglev.Event - 8, // 36: maglev.Maglev.CheckConfig:output_type -> maglev.CheckConfigResponse - 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 + 11, // 24: maglev.Maglev.GetVPPInfo:input_type -> maglev.GetVPPInfoRequest + 15, // 25: maglev.Maglev.ListFrontends:output_type -> maglev.ListFrontendsResponse + 18, // 26: maglev.Maglev.GetFrontend:output_type -> maglev.FrontendInfo + 19, // 27: maglev.Maglev.ListBackends:output_type -> maglev.ListBackendsResponse + 24, // 28: maglev.Maglev.GetBackend:output_type -> maglev.BackendInfo + 24, // 29: maglev.Maglev.PauseBackend:output_type -> maglev.BackendInfo + 24, // 30: maglev.Maglev.ResumeBackend:output_type -> maglev.BackendInfo + 24, // 31: maglev.Maglev.EnableBackend:output_type -> maglev.BackendInfo + 24, // 32: maglev.Maglev.DisableBackend:output_type -> maglev.BackendInfo + 20, // 33: maglev.Maglev.ListHealthChecks:output_type -> maglev.ListHealthChecksResponse + 23, // 34: maglev.Maglev.GetHealthCheck:output_type -> maglev.HealthCheckInfo + 18, // 35: maglev.Maglev.SetFrontendPoolBackendWeight:output_type -> maglev.FrontendInfo + 30, // 36: maglev.Maglev.WatchEvents:output_type -> maglev.Event + 8, // 37: maglev.Maglev.CheckConfig:output_type -> maglev.CheckConfigResponse + 10, // 38: maglev.Maglev.ReloadConfig:output_type -> maglev.ReloadConfigResponse + 12, // 39: maglev.Maglev.GetVPPInfo:output_type -> maglev.VPPInfo + 25, // [25:40] is the sub-list for method output_type + 10, // [10:25] 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 extendee 0, // [0:10] is the sub-list for field type_name @@ -1947,8 +2083,8 @@ func file_proto_maglev_proto_init() { if File_proto_maglev_proto != nil { return } - file_proto_maglev_proto_msgTypes[12].OneofWrappers = []any{} - file_proto_maglev_proto_msgTypes[28].OneofWrappers = []any{ + file_proto_maglev_proto_msgTypes[14].OneofWrappers = []any{} + file_proto_maglev_proto_msgTypes[30].OneofWrappers = []any{ (*Event_Log)(nil), (*Event_Backend)(nil), (*Event_Frontend)(nil), @@ -1959,7 +2095,7 @@ func file_proto_maglev_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_proto_maglev_proto_rawDesc), len(file_proto_maglev_proto_rawDesc)), NumEnums: 0, - NumMessages: 29, + NumMessages: 31, NumExtensions: 0, NumServices: 1, }, diff --git a/internal/grpcapi/maglev_grpc.pb.go b/internal/grpcapi/maglev_grpc.pb.go index 91b9350..b181d54 100644 --- a/internal/grpcapi/maglev_grpc.pb.go +++ b/internal/grpcapi/maglev_grpc.pb.go @@ -33,6 +33,7 @@ const ( Maglev_WatchEvents_FullMethodName = "/maglev.Maglev/WatchEvents" Maglev_CheckConfig_FullMethodName = "/maglev.Maglev/CheckConfig" Maglev_ReloadConfig_FullMethodName = "/maglev.Maglev/ReloadConfig" + Maglev_GetVPPInfo_FullMethodName = "/maglev.Maglev/GetVPPInfo" ) // MaglevClient is the client API for Maglev service. @@ -55,6 +56,7 @@ type MaglevClient interface { WatchEvents(ctx context.Context, in *WatchRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[Event], error) CheckConfig(ctx context.Context, in *CheckConfigRequest, opts ...grpc.CallOption) (*CheckConfigResponse, error) ReloadConfig(ctx context.Context, in *ReloadConfigRequest, opts ...grpc.CallOption) (*ReloadConfigResponse, error) + GetVPPInfo(ctx context.Context, in *GetVPPInfoRequest, opts ...grpc.CallOption) (*VPPInfo, error) } type maglevClient struct { @@ -214,6 +216,16 @@ func (c *maglevClient) ReloadConfig(ctx context.Context, in *ReloadConfigRequest return out, nil } +func (c *maglevClient) GetVPPInfo(ctx context.Context, in *GetVPPInfoRequest, opts ...grpc.CallOption) (*VPPInfo, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(VPPInfo) + err := c.cc.Invoke(ctx, Maglev_GetVPPInfo_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + // MaglevServer is the server API for Maglev service. // All implementations must embed UnimplementedMaglevServer // for forward compatibility. @@ -234,6 +246,7 @@ type MaglevServer interface { WatchEvents(*WatchRequest, grpc.ServerStreamingServer[Event]) error CheckConfig(context.Context, *CheckConfigRequest) (*CheckConfigResponse, error) ReloadConfig(context.Context, *ReloadConfigRequest) (*ReloadConfigResponse, error) + GetVPPInfo(context.Context, *GetVPPInfoRequest) (*VPPInfo, error) mustEmbedUnimplementedMaglevServer() } @@ -286,6 +299,9 @@ func (UnimplementedMaglevServer) CheckConfig(context.Context, *CheckConfigReques func (UnimplementedMaglevServer) ReloadConfig(context.Context, *ReloadConfigRequest) (*ReloadConfigResponse, error) { return nil, status.Error(codes.Unimplemented, "method ReloadConfig not implemented") } +func (UnimplementedMaglevServer) GetVPPInfo(context.Context, *GetVPPInfoRequest) (*VPPInfo, error) { + return nil, status.Error(codes.Unimplemented, "method GetVPPInfo not implemented") +} func (UnimplementedMaglevServer) mustEmbedUnimplementedMaglevServer() {} func (UnimplementedMaglevServer) testEmbeddedByValue() {} @@ -552,6 +568,24 @@ func _Maglev_ReloadConfig_Handler(srv interface{}, ctx context.Context, dec func return interceptor(ctx, in, info, handler) } +func _Maglev_GetVPPInfo_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetVPPInfoRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MaglevServer).GetVPPInfo(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Maglev_GetVPPInfo_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MaglevServer).GetVPPInfo(ctx, req.(*GetVPPInfoRequest)) + } + return interceptor(ctx, in, info, handler) +} + // Maglev_ServiceDesc is the grpc.ServiceDesc for Maglev service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -611,6 +645,10 @@ var Maglev_ServiceDesc = grpc.ServiceDesc{ MethodName: "ReloadConfig", Handler: _Maglev_ReloadConfig_Handler, }, + { + MethodName: "GetVPPInfo", + Handler: _Maglev_GetVPPInfo_Handler, + }, }, Streams: []grpc.StreamDesc{ { diff --git a/internal/grpcapi/server.go b/internal/grpcapi/server.go index cf7b929..be20c0d 100644 --- a/internal/grpcapi/server.go +++ b/internal/grpcapi/server.go @@ -13,6 +13,7 @@ import ( "git.ipng.ch/ipng/vpp-maglev/internal/checker" "git.ipng.ch/ipng/vpp-maglev/internal/config" "git.ipng.ch/ipng/vpp-maglev/internal/health" + "git.ipng.ch/ipng/vpp-maglev/internal/vpp" ) // Server implements the MaglevServer gRPC interface. @@ -22,16 +23,17 @@ type Server struct { checker *checker.Checker logs *LogBroadcaster configPath string + vppClient *vpp.Client // nil when VPP integration is disabled } // NewServer creates a Server backed by the given Checker. logs may be nil, in // which case log events are never sent to WatchEvents streams. configPath is // used by CheckConfig to reload and validate the configuration file on demand. -// The provided context controls the lifetime of streaming RPCs: cancelling it -// closes all active WatchEvents streams so that grpc.Server.GracefulStop can -// complete. -func NewServer(ctx context.Context, c *checker.Checker, logs *LogBroadcaster, configPath string) *Server { - return &Server{ctx: ctx, checker: c, logs: logs, configPath: configPath} +// vppClient may be nil if VPP integration is disabled. The provided context +// controls the lifetime of streaming RPCs: cancelling it closes all active +// WatchEvents streams so that grpc.Server.GracefulStop can complete. +func NewServer(ctx context.Context, c *checker.Checker, logs *LogBroadcaster, configPath string, vppClient *vpp.Client) *Server { + return &Server{ctx: ctx, checker: c, logs: logs, configPath: configPath, vppClient: vppClient} } // ListFrontends returns the names of all configured frontends. @@ -257,6 +259,29 @@ func (s *Server) doReloadConfig() *ReloadConfigResponse { return &ReloadConfigResponse{Ok: true} } +// GetVPPInfo returns VPP version and runtime information. +func (s *Server) GetVPPInfo(_ context.Context, _ *GetVPPInfoRequest) (*VPPInfo, error) { + if s.vppClient == nil { + return nil, status.Error(codes.Unavailable, "VPP integration is disabled") + } + info, err := s.vppClient.GetInfo() + if err != nil { + return nil, status.Errorf(codes.Unavailable, "%v", err) + } + var boottimeNs int64 + if !info.BootTime.IsZero() { + boottimeNs = info.BootTime.UnixNano() + } + return &VPPInfo{ + Version: info.Version, + BuildDate: info.BuildDate, + BuildDirectory: info.BuildDirectory, + Pid: info.PID, + BoottimeNs: boottimeNs, + ConnecttimeNs: info.ConnectedSince.UnixNano(), + }, nil +} + // ---- conversion helpers ---------------------------------------------------- func frontendToProto(name string, fe config.Frontend) *FrontendInfo { diff --git a/internal/grpcapi/server_test.go b/internal/grpcapi/server_test.go index b0cdcfb..e46d04f 100644 --- a/internal/grpcapi/server_test.go +++ b/internal/grpcapi/server_test.go @@ -62,7 +62,7 @@ func startTestServer(t *testing.T, ctx context.Context, c *checker.Checker) (Mag t.Fatalf("listen: %v", err) } srv := grpc.NewServer() - RegisterMaglevServer(srv, NewServer(ctx, c, nil, "")) + RegisterMaglevServer(srv, NewServer(ctx, c, nil, "", nil)) go srv.Serve(lis) //nolint:errcheck conn, err := grpc.NewClient(lis.Addr().String(), diff --git a/internal/vpp/client.go b/internal/vpp/client.go new file mode 100644 index 0000000..e06cc1e --- /dev/null +++ b/internal/vpp/client.go @@ -0,0 +1,265 @@ +// Copyright (c) 2026, Pim van Pelt + +// Package vpp manages the connection to a local VPP instance over its +// binary API and stats sockets. The Client reconnects automatically when +// VPP restarts. +package vpp + +import ( + "context" + "log/slog" + "sync" + "time" + + "go.fd.io/govpp/adapter" + "go.fd.io/govpp/adapter/socketclient" + "go.fd.io/govpp/adapter/statsclient" + "go.fd.io/govpp/api" + "go.fd.io/govpp/binapi/vpe" + "go.fd.io/govpp/core" +) + +const retryInterval = 5 * time.Second +const pingInterval = 10 * time.Second + +// Info holds VPP version and connection metadata, populated on connect. +type Info struct { + Version string + BuildDate string + BuildDirectory string + PID uint32 + BootTime time.Time // when VPP started (from /sys/boottime stats counter) + ConnectedSince time.Time // when maglevd connected to VPP +} + +// Client manages connections to both the VPP API and stats sockets. +// Both connections are treated as a unit: if either drops, both are +// torn down and re-established together. +type Client struct { + apiAddr string + statsAddr string + + mu sync.Mutex + apiConn *core.Connection + statsConn *core.StatsConnection + statsClient adapter.StatsAPI // raw adapter for DumpStats + info Info // populated on successful connect +} + +// New creates a Client for the given socket paths. +func New(apiAddr, statsAddr string) *Client { + return &Client{apiAddr: apiAddr, statsAddr: statsAddr} +} + +// Run connects to VPP and maintains the connection until ctx is cancelled. +// If VPP is unavailable or restarts, Run reconnects automatically. +func (c *Client) Run(ctx context.Context) { + for { + if err := c.connect(); err != nil { + slog.Debug("vpp-connect-failed", "err", err) + select { + case <-ctx.Done(): + return + case <-time.After(retryInterval): + continue + } + } + + // Fetch version info and record connect time. + // fetchInfo uses NewAPIChannel and statsClient which both take c.mu, + // so we must not hold c.mu here. + info := c.fetchInfo() + c.mu.Lock() + c.info = info + c.mu.Unlock() + slog.Info("vpp-connect", "version", c.info.Version, + "build-date", c.info.BuildDate, + "pid", c.info.PID, + "api", c.apiAddr, "stats", c.statsAddr) + + // Hold the connection, pinging periodically to detect VPP restarts. + c.monitor(ctx) + + // If ctx is done we're shutting down; otherwise VPP dropped and we retry. + c.disconnect() + if ctx.Err() != nil { + return + } + slog.Warn("vpp-disconnect", "msg", "connection lost, reconnecting") + } +} + +// IsConnected returns true if both API and stats connections are active. +func (c *Client) IsConnected() bool { + c.mu.Lock() + defer c.mu.Unlock() + return c.apiConn != nil && c.statsConn != nil +} + +// NewAPIChannel creates a new API channel for sending VPP binary API requests. +// Returns an error if the API connection is not established. +func (c *Client) NewAPIChannel() (api.Channel, error) { + c.mu.Lock() + conn := c.apiConn + c.mu.Unlock() + if conn == nil { + return nil, errNotConnected + } + return conn.NewAPIChannel() +} + +// StatsConnection returns the stats connection, or nil if not connected. +func (c *Client) StatsConnection() *core.StatsConnection { + c.mu.Lock() + defer c.mu.Unlock() + return c.statsConn +} + +// GetInfo returns the VPP version and connection metadata, or an error +// if VPP is not connected. +func (c *Client) GetInfo() (Info, error) { + c.mu.Lock() + defer c.mu.Unlock() + if c.apiConn == nil { + return Info{}, errNotConnected + } + return c.info, nil +} + +// connect establishes both API and stats connections. If either fails, +// both are torn down. +func (c *Client) connect() error { + sc := socketclient.NewVppClient(c.apiAddr) + sc.SetClientName("vpp-maglev") + apiConn, err := core.Connect(sc) + if err != nil { + return err + } + + stc := statsclient.NewStatsClient(c.statsAddr) + statsConn, err := core.ConnectStats(stc) + if err != nil { + safeDisconnectAPI(apiConn) + return err + } + + c.mu.Lock() + c.apiConn = apiConn + c.statsConn = statsConn + c.statsClient = stc + c.mu.Unlock() + return nil +} + +// disconnect tears down both connections. +func (c *Client) disconnect() { + c.mu.Lock() + apiConn := c.apiConn + statsConn := c.statsConn + c.apiConn = nil + c.statsConn = nil + c.statsClient = nil + c.info = Info{} + c.mu.Unlock() + + safeDisconnectAPI(apiConn) + safeDisconnectStats(statsConn) +} + +// monitor blocks until the context is cancelled or a liveness ping fails. +func (c *Client) monitor(ctx context.Context) { + ticker := time.NewTicker(pingInterval) + defer ticker.Stop() + for { + select { + case <-ctx.Done(): + return + case <-ticker.C: + if !c.ping() { + return + } + } + } +} + +// ping sends a control_ping to VPP and returns true if it succeeds. +func (c *Client) ping() bool { + ch, err := c.NewAPIChannel() + if err != nil { + return false + } + defer ch.Close() + + req := &core.ControlPing{} + reply := &core.ControlPingReply{} + if err := ch.SendRequest(req).ReceiveReply(reply); err != nil { + slog.Debug("vpp-ping-failed", "err", err) + return false + } + return true +} + +// fetchInfo queries VPP for version info, PID, and boot time. +// Must be called after connect succeeds (apiConn and statsClient are set). +func (c *Client) fetchInfo() Info { + info := Info{ConnectedSince: time.Now()} + + ch, err := c.NewAPIChannel() + if err != nil { + return info + } + defer ch.Close() + + ver := &vpe.ShowVersionReply{} + if err := ch.SendRequest(&vpe.ShowVersion{}).ReceiveReply(ver); err == nil { + info.Version = ver.Version + info.BuildDate = ver.BuildDate + info.BuildDirectory = ver.BuildDirectory + } + + ping := &core.ControlPingReply{} + if err := ch.SendRequest(&core.ControlPing{}).ReceiveReply(ping); err == nil { + info.PID = ping.VpePID + } + + // Read VPP boot time from the stats segment. + c.mu.Lock() + sc := c.statsClient + c.mu.Unlock() + if sc != nil { + if entries, err := sc.DumpStats("/sys/boottime"); err == nil { + for _, e := range entries { + if s, ok := e.Data.(adapter.ScalarStat); ok && s != 0 { + info.BootTime = time.Unix(int64(s), 0) + } + } + } + } + + return info +} + +// safeDisconnectAPI disconnects an API connection, recovering from any panic +// that GoVPP may raise on a stale connection. +func safeDisconnectAPI(conn *core.Connection) { + if conn == nil { + return + } + defer func() { recover() }() //nolint:errcheck + conn.Disconnect() +} + +// safeDisconnectStats disconnects a stats connection, recovering from panics. +func safeDisconnectStats(conn *core.StatsConnection) { + if conn == nil { + return + } + defer func() { recover() }() //nolint:errcheck + conn.Disconnect() +} + +type vppError struct{ msg string } + +func (e *vppError) Error() string { return e.msg } + +var errNotConnected = &vppError{msg: "VPP API connection not established"} diff --git a/proto/maglev.proto b/proto/maglev.proto index af87a7a..16ce6f1 100644 --- a/proto/maglev.proto +++ b/proto/maglev.proto @@ -20,6 +20,7 @@ service Maglev { rpc WatchEvents(WatchRequest) returns (stream Event); rpc CheckConfig(CheckConfigRequest) returns (CheckConfigResponse); rpc ReloadConfig(ReloadConfigRequest) returns (ReloadConfigResponse); + rpc GetVPPInfo(GetVPPInfoRequest) returns (VPPInfo); } // ---- requests --------------------------------------------------------------- @@ -63,6 +64,17 @@ message ReloadConfigResponse { string reload_error = 4; // set when config is valid but the reload itself failed } +message GetVPPInfoRequest {} + +message VPPInfo { + string version = 1; + string build_date = 2; + string build_directory = 3; + uint32 pid = 4; + int64 boottime_ns = 5; // unix timestamp (ns) when VPP started (from /sys/boottime) + int64 connecttime_ns = 6; // unix timestamp (ns) when maglevd connected to VPP +} + message SetWeightRequest { string frontend = 1; string pool = 2; diff --git a/tests/01-maglevd/maglevd-lab/start.sh b/tests/01-maglevd/maglevd-lab/start.sh index 25314f9..3ed436a 100755 --- a/tests/01-maglevd/maglevd-lab/start.sh +++ b/tests/01-maglevd/maglevd-lab/start.sh @@ -4,4 +4,4 @@ case "$ARCH" in x86_64) ARCH=amd64 ;; aarch64) ARCH=arm64 ;; esac -exec /opt/maglev/build/${ARCH}/maglevd --config /etc/maglev/maglev.yaml --log-level debug +exec /opt/maglev/build/${ARCH}/maglevd --config /etc/maglev/maglev.yaml --vpp-api-addr "" --log-level debug