Add GoVPP integration and GetVPPInfo gRPC call

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).
This commit is contained in:
2026-04-11 22:03:22 +02:00
parent 74448cf6d0
commit 3227263d68
13 changed files with 690 additions and 120 deletions

View File

@@ -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,
},

View File

@@ -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{
{

View File

@@ -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 {

View File

@@ -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(),

265
internal/vpp/client.go Normal file
View File

@@ -0,0 +1,265 @@
// Copyright (c) 2026, Pim van Pelt <pim@ipng.ch>
// 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"}