diff --git a/Dockerfile b/Dockerfile index a6ce2f2..86653e3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -14,14 +14,13 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ iproute2 \ && rm -rf /var/lib/apt/lists/* -COPY --from=builder /src/bin/healthchecker /usr/local/bin/healthchecker +COPY --from=builder /src/bin/maglevd /usr/local/bin/maglevd # Required capabilities: -# CAP_NET_ADMIN — create/delete GRE tunnel interfaces via netlink -# CAP_NET_RAW — open raw ICMP sockets for health probing +# CAP_NET_RAW — open raw ICMP sockets for health probing # # Grant these in your container runtime, e.g.: -# docker run --cap-add NET_ADMIN --cap-add NET_RAW ... +# docker run --cap-add NET_RAW ... # or in Kubernetes via securityContext.capabilities.add -ENTRYPOINT ["/usr/local/bin/healthchecker"] +ENTRYPOINT ["/usr/local/bin/maglevd"] diff --git a/Makefile b/Makefile index 8cad6b9..189f5f2 100644 --- a/Makefile +++ b/Makefile @@ -1,15 +1,15 @@ -BINARY := healthchecker +BINARY := maglevd MODULE := git.ipng.ch/ipng/vpp-maglev PROTO_DIR := proto -PROTO_FILE := $(PROTO_DIR)/healthchecker.proto -GEN_FILES := internal/grpcapi/healthchecker.pb.go internal/grpcapi/healthchecker_grpc.pb.go +PROTO_FILE := $(PROTO_DIR)/maglev.proto +GEN_FILES := internal/grpcapi/maglev.pb.go internal/grpcapi/maglev_grpc.pb.go .PHONY: all build test proto lint clean all: build build: $(GEN_FILES) - go build -o bin/$(BINARY) ./cmd/$(BINARY)/ + go build -o bin/$(BINARY) ./cmd/maglevd/ test: $(GEN_FILES) go test ./... diff --git a/cmd/healthchecker/main.go b/cmd/maglevd/main.go similarity index 93% rename from cmd/healthchecker/main.go rename to cmd/maglevd/main.go index fa211cf..f1dbc9a 100644 --- a/cmd/healthchecker/main.go +++ b/cmd/maglevd/main.go @@ -43,7 +43,7 @@ func run() error { if err != nil { return fmt.Errorf("load config: %w", err) } - slog.Info("config-loaded", "path", *configPath, "vips", len(cfg.VIPs)) + slog.Info("config-loaded", "path", *configPath, "frontends", len(cfg.Frontends)) // ---- checker ------------------------------------------------------------ chkr := checker.New(cfg) @@ -63,7 +63,7 @@ func run() error { return fmt.Errorf("listen %s: %w", *grpcAddr, err) } srv := grpc.NewServer() - grpcapi.RegisterHealthCheckerServer(srv, grpcapi.NewServer(chkr)) + grpcapi.RegisterMaglevServer(srv, grpcapi.NewServer(chkr)) slog.Info("grpc-listening", "addr", *grpcAddr) go func() { @@ -89,7 +89,7 @@ func run() error { slog.Error("checker-reload-error", "err", err) continue } - slog.Info("config-reload-done", "vips", len(newCfg.VIPs)) + slog.Info("config-reload-done", "frontends", len(newCfg.Frontends)) case syscall.SIGTERM, syscall.SIGINT: slog.Info("shutdown", "signal", sig) diff --git a/internal/checker/checker.go b/internal/checker/checker.go index 747f7a3..9f10807 100644 --- a/internal/checker/checker.go +++ b/internal/checker/checker.go @@ -4,6 +4,7 @@ import ( "context" "log/slog" "net" + "sort" "sync" "time" @@ -12,30 +13,35 @@ import ( "git.ipng.ch/ipng/vpp-maglev/internal/prober" ) -// Event is emitted on every backend state transition. -type Event struct { - VIPName string - Backend net.IP - Transition health.Transition +// BackendSnapshot combines the live health state with the config entry for a backend. +type BackendSnapshot struct { + Health *health.Backend + Config config.Backend } -type backendKey struct { - VIPName string - Backend string // net.IP.String() +// Event is emitted on every backend state transition, once per frontend that +// references the backend. +type Event struct { + FrontendName string + BackendName string + Backend net.IP + Transition health.Transition } type worker struct { backend *health.Backend hc config.HealthCheck - vip config.VIP + entry config.Backend cancel context.CancelFunc } -// Checker orchestrates health probing for all VIP:backend tuples. +// Checker orchestrates health probing for all backends. +// Each backend is probed exactly once, regardless of how many frontends +// reference it. type Checker struct { - cfg *config.Frontend + cfg *config.Config mu sync.RWMutex - workers map[backendKey]*worker + workers map[string]*worker // keyed by backend name subsMu sync.Mutex nextID int @@ -44,10 +50,10 @@ type Checker struct { } // New creates a Checker. Call Run to start probing. -func New(cfg *config.Frontend) *Checker { +func New(cfg *config.Config) *Checker { return &Checker{ cfg: cfg, - workers: make(map[backendKey]*worker), + workers: make(map[string]*worker), subs: make(map[int]chan Event), eventCh: make(chan Event, 256), } @@ -58,13 +64,11 @@ func (c *Checker) Run(ctx context.Context) error { go c.fanOut(ctx) c.mu.Lock() - total := totalBackends(c.cfg) - pos := 0 - for vipName, vip := range c.cfg.VIPs { - for _, backend := range vip.Backends { - c.startWorker(ctx, vipName, vip, backend, pos, total) - pos++ - } + names := activeBackendNames(c.cfg) + for i, name := range names { + b := c.cfg.Backends[name] + hc := c.cfg.HealthChecks[b.HealthCheck] + c.startWorker(ctx, name, b, hc, i, len(names)) } c.mu.Unlock() @@ -73,55 +77,45 @@ func (c *Checker) Run(ctx context.Context) error { } // Reload applies a new config without restarting the process. -// New tuples are added, removed tuples are stopped, changed tuples are restarted. -// Existing tuples with unchanged healthcheck config continue uninterrupted. -func (c *Checker) Reload(ctx context.Context, cfg *config.Frontend) error { +// New backends are added, removed backends are stopped, changed backends are +// restarted. Backends whose healthcheck config is unchanged continue +// uninterrupted, even if the set of frontends referencing them changes. +func (c *Checker) Reload(ctx context.Context, cfg *config.Config) error { c.mu.Lock() defer c.mu.Unlock() - type desired struct { - vipName string - vip config.VIP - backend net.IP - } - desiredMap := map[backendKey]desired{} - for vipName, vip := range cfg.VIPs { - for _, backend := range vip.Backends { - key := backendKey{VIPName: vipName, Backend: backend.String()} - desiredMap[key] = desired{vipName: vipName, vip: vip, backend: backend} - } + desired := map[string]struct{}{} + for _, name := range activeBackendNames(cfg) { + desired[name] = struct{}{} } - // Stop workers no longer in config. - for key, w := range c.workers { - if _, ok := desiredMap[key]; !ok { - slog.Info("backend-stop", "vip", key.VIPName, "backend", key.Backend) + // Stop workers no longer needed. + for name, w := range c.workers { + if _, ok := desired[name]; !ok { + slog.Info("backend-stop", "backend", name) w.cancel() - delete(c.workers, key) + delete(c.workers, name) } } // Add new or restart changed workers. - total := len(desiredMap) - pos := 0 - for key, d := range desiredMap { - if w, ok := c.workers[key]; ok { - if healthCheckEqual(w.hc, d.vip.HealthCheck) { - pos++ + names := activeBackendNames(cfg) + for i, name := range names { + b := cfg.Backends[name] + hc := cfg.HealthChecks[b.HealthCheck] + if w, ok := c.workers[name]; ok { + if healthCheckEqual(w.hc, hc) { + // Update entry metadata (weight, etc.) in place without restart. + w.entry = b continue } - slog.Info("backend-restart", "vip", key.VIPName, "backend", key.Backend) + slog.Info("backend-restart", "backend", name) w.cancel() - w.hc = d.vip.HealthCheck - w.vip = d.vip - wCtx, cancel := context.WithCancel(ctx) - w.cancel = cancel - go c.runProbe(wCtx, key, 0, 1) // no stagger on reload + c.startWorker(ctx, name, b, hc, i, len(names)) } else { - slog.Info("backend-start", "vip", d.vipName, "backend", d.backend) - c.startWorker(ctx, d.vipName, d.vip, d.backend, pos, total) + slog.Info("backend-start", "backend", name) + c.startWorker(ctx, name, b, hc, i, len(names)) } - pos++ } c.cfg = cfg @@ -145,109 +139,142 @@ func (c *Checker) Subscribe() (<-chan Event, func()) { } } -// ListVIPs returns the names of all configured VIPs. -func (c *Checker) ListVIPs() []string { +// ListFrontends returns the names of all configured frontends. +func (c *Checker) ListFrontends() []string { c.mu.RLock() defer c.mu.RUnlock() - names := make([]string, 0, len(c.cfg.VIPs)) - for name := range c.cfg.VIPs { + names := make([]string, 0, len(c.cfg.Frontends)) + for name := range c.cfg.Frontends { names = append(names, name) } return names } -// GetVIP returns the VIP config for the given name. -func (c *Checker) GetVIP(name string) (config.VIP, bool) { +// GetFrontend returns the frontend config for the given name. +func (c *Checker) GetFrontend(name string) (config.Frontend, bool) { c.mu.RLock() defer c.mu.RUnlock() - v, ok := c.cfg.VIPs[name] + v, ok := c.cfg.Frontends[name] return v, ok } -// ListBackends returns the backend states for all backends of a VIP. -func (c *Checker) ListBackends(vipName string) []*health.Backend { +// ListHealthChecks returns the names of all configured health checks, sorted. +func (c *Checker) ListHealthChecks() []string { c.mu.RLock() defer c.mu.RUnlock() + names := make([]string, 0, len(c.cfg.HealthChecks)) + for name := range c.cfg.HealthChecks { + names = append(names, name) + } + sort.Strings(names) + return names +} + +// GetHealthCheck returns the config for a health check by name. +func (c *Checker) GetHealthCheck(name string) (config.HealthCheck, bool) { + c.mu.RLock() + defer c.mu.RUnlock() + hc, ok := c.cfg.HealthChecks[name] + return hc, ok +} + +// ListBackends returns the names of all active backends. +func (c *Checker) ListBackends() []string { + c.mu.RLock() + defer c.mu.RUnlock() + names := make([]string, 0, len(c.workers)) + for name := range c.workers { + names = append(names, name) + } + sort.Strings(names) + return names +} + +// ListFrontendBackends returns the backend health states for all backends of a frontend. +func (c *Checker) ListFrontendBackends(frontendName string) []*health.Backend { + c.mu.RLock() + defer c.mu.RUnlock() + fe, ok := c.cfg.Frontends[frontendName] + if !ok { + return nil + } var out []*health.Backend - for key, w := range c.workers { - if key.VIPName == vipName { + for _, name := range fe.Backends { + if w, ok := c.workers[name]; ok { out = append(out, w.backend) } } return out } -// GetBackend returns the backend state for a specific VIP:backend tuple. -func (c *Checker) GetBackend(vipName, backendAddr string) (*health.Backend, bool) { +// GetBackend returns a snapshot of the health state and config for a backend by name. +func (c *Checker) GetBackend(name string) (BackendSnapshot, bool) { c.mu.RLock() defer c.mu.RUnlock() - key := backendKey{VIPName: vipName, Backend: backendAddr} - w, ok := c.workers[key] + w, ok := c.workers[name] if !ok { - return nil, false + return BackendSnapshot{}, false } - return w.backend, true + return BackendSnapshot{Health: w.backend, Config: w.entry}, true } -// PauseBackend pauses health checking for a specific backend. -func (c *Checker) PauseBackend(vipName, backendAddr string) (*health.Backend, bool) { +// PauseBackend pauses health checking for a backend by name. +func (c *Checker) PauseBackend(name string) (BackendSnapshot, bool) { c.mu.Lock() defer c.mu.Unlock() - key := backendKey{VIPName: vipName, Backend: backendAddr} - w, ok := c.workers[key] + w, ok := c.workers[name] if !ok { - return nil, false + return BackendSnapshot{}, false } maxHistory := c.cfg.HealthChecker.TransitionHistory if w.backend.Pause(maxHistory) { - slog.Info("backend-pause", "vip", vipName, "backend", backendAddr) - c.emit(Event{VIPName: vipName, Backend: w.backend.Address, Transition: w.backend.Transitions[0]}) + slog.Info("backend-pause", "backend", name) + c.emitForBackend(name, w.backend.Address, w.backend.Transitions[0]) } - return w.backend, true + return BackendSnapshot{Health: w.backend, Config: w.entry}, true } -// ResumeBackend resumes health checking for a specific backend. -func (c *Checker) ResumeBackend(vipName, backendAddr string) (*health.Backend, bool) { +// ResumeBackend resumes health checking for a backend by name. +func (c *Checker) ResumeBackend(name string) (BackendSnapshot, bool) { c.mu.Lock() defer c.mu.Unlock() - key := backendKey{VIPName: vipName, Backend: backendAddr} - w, ok := c.workers[key] + w, ok := c.workers[name] if !ok { - return nil, false + return BackendSnapshot{}, false } maxHistory := c.cfg.HealthChecker.TransitionHistory if w.backend.Resume(maxHistory) { - slog.Info("backend-resume", "vip", vipName, "backend", backendAddr) - c.emit(Event{VIPName: vipName, Backend: w.backend.Address, Transition: w.backend.Transitions[0]}) + slog.Info("backend-resume", "backend", name) + c.emitForBackend(name, w.backend.Address, w.backend.Transitions[0]) } - return w.backend, true + return BackendSnapshot{Health: w.backend, Config: w.entry}, true } // ---- internal -------------------------------------------------------------- // startWorker creates a Backend and launches a probe goroutine. -// pos and total are used to compute the startup stagger delay. // Must be called with c.mu held. -func (c *Checker) startWorker(ctx context.Context, vipName string, vip config.VIP, backend net.IP, pos, total int) { - key := backendKey{VIPName: vipName, Backend: backend.String()} +func (c *Checker) startWorker(ctx context.Context, name string, entry config.Backend, hc config.HealthCheck, pos, total int) { + rise, fall := hc.Rise, hc.Fall + if entry.HealthCheck == "" { + // No healthcheck: one synthetic pass drives the backend to Up immediately. + rise, fall = 1, 1 + } wCtx, cancel := context.WithCancel(ctx) - hc := vip.HealthCheck w := &worker{ - backend: health.New(vipName, backend, hc.Rise, hc.Fall), + backend: health.New(name, entry.Address, rise, fall), hc: hc, - vip: vip, + entry: entry, cancel: cancel, } - c.workers[key] = w - go c.runProbe(wCtx, key, pos, total) + c.workers[name] = w + go c.runProbe(wCtx, name, pos, total) } // runProbe is the per-backend probe loop. -// pos and total drive the initial stagger: delay = interval * pos / total. -func (c *Checker) runProbe(ctx context.Context, key backendKey, pos, total int) { - // Stagger initial probe to spread startup load. +func (c *Checker) runProbe(ctx context.Context, name string, pos, total int) { c.mu.RLock() - w, ok := c.workers[key] + w, ok := c.workers[name] if !ok { c.mu.RUnlock() return @@ -265,15 +292,21 @@ func (c *Checker) runProbe(ctx context.Context, key backendKey, pos, total int) for { c.mu.RLock() - w, ok := c.workers[key] + w, ok := c.workers[name] if !ok { c.mu.RUnlock() return } hc := w.hc - vip := w.vip + entry := w.entry maxHistory := c.cfg.HealthChecker.TransitionHistory - sleepFor := w.backend.NextInterval(hc.Interval, hc.FastInterval, hc.DownInterval) + netns := c.cfg.HealthChecker.Netns + var sleepFor time.Duration + if entry.HealthCheck == "" { + sleepFor = 30 * time.Second + } else { + sleepFor = w.backend.NextInterval(hc.Interval, hc.FastInterval, hc.DownInterval) + } c.mu.RUnlock() select { @@ -282,42 +315,44 @@ func (c *Checker) runProbe(ctx context.Context, key backendKey, pos, total int) case <-time.After(sleepFor): } - // Determine source IP based on target address family. - backendIP := net.ParseIP(key.Backend) - var probeSrc net.IP - if backendIP.To4() != nil { - probeSrc = c.cfg.ProbeIPv4Src + var result health.ProbeResult + if entry.HealthCheck == "" { + // No healthcheck configured: synthesise a passing result so the + // backend is assumed healthy without any network activity. + result = health.ProbeResult{OK: true, Layer: health.LayerL7, Code: "L7OK"} } else { - probeSrc = c.cfg.ProbeIPv6Src + var probeSrc net.IP + if entry.Address.To4() != nil { + probeSrc = hc.ProbeIPv4Src + } else { + probeSrc = hc.ProbeIPv6Src + } + pcfg := prober.ProbeConfig{ + Target: entry.Address, + Port: hc.Port, + ProbeSrc: probeSrc, + HealthCheckNetns: netns, + Timeout: hc.Timeout, + HTTP: hc.HTTP, + TCP: hc.TCP, + } + probeCtx, cancel := context.WithTimeout(ctx, hc.Timeout) + slog.Debug("probe-start", "backend", name, "type", hc.Type) + start := time.Now() + result = prober.ForType(hc.Type)(probeCtx, pcfg) + cancel() + slog.Debug("probe-done", + "backend", name, + "type", hc.Type, + "ok", result.OK, + "code", result.Code, + "detail", result.Detail, + "elapsed", time.Since(start).Round(time.Millisecond).String(), + ) } - pcfg := prober.ProbeConfig{ - Target: backendIP, - Port: vip.Port, - ProbeSrc: probeSrc, - HealthCheckNetns: c.cfg.HealthCheckNetns, - Timeout: hc.Timeout, - HTTP: hc.HTTP, - TCP: hc.TCP, - } - - probeCtx, cancel := context.WithTimeout(ctx, hc.Timeout) - slog.Debug("probe-start", "vip", key.VIPName, "backend", key.Backend, "type", hc.Type) - start := time.Now() - result := prober.ForType(hc.Type)(probeCtx, pcfg) - cancel() - slog.Debug("probe-done", - "vip", key.VIPName, - "backend", key.Backend, - "type", hc.Type, - "ok", result.OK, - "code", result.Code, - "detail", result.Detail, - "elapsed", time.Since(start).Round(time.Millisecond).String(), - ) - c.mu.Lock() - w, exists := c.workers[key] + w, exists := c.workers[name] if !exists { c.mu.Unlock() return @@ -326,26 +361,38 @@ func (c *Checker) runProbe(ctx context.Context, key backendKey, pos, total int) t := w.backend.Transitions[0] addr := w.backend.Address slog.Info("backend-transition", - "vip", key.VIPName, - "backend", key.Backend, + "backend", name, "from", t.From.String(), "to", t.To.String(), "code", result.Code, "detail", result.Detail, ) - c.emit(Event{VIPName: key.VIPName, Backend: addr, Transition: t}) + c.emitForBackend(name, addr, t) } c.mu.Unlock() } } +// emitForBackend emits one Event per frontend that references backendName. +// Must be called with c.mu held. +func (c *Checker) emitForBackend(backendName string, addr net.IP, t health.Transition) { + for feName, fe := range c.cfg.Frontends { + for _, name := range fe.Backends { + if name == backendName { + c.emit(Event{FrontendName: feName, BackendName: backendName, Backend: addr, Transition: t}) + break + } + } + } +} + // emit sends an event to the internal fan-out channel (non-blocking). // Must be called with c.mu held. func (c *Checker) emit(e Event) { select { case c.eventCh <- e: default: - slog.Warn("event-drop", "vip", e.VIPName, "backend", e.Backend) + slog.Warn("event-drop", "frontend", e.FrontendName, "backend", e.BackendName) } } @@ -418,20 +465,29 @@ func tcpParamsEqual(a, b *config.TCPParams) bool { a.InsecureSkipVerify == b.InsecureSkipVerify } -// staggerDelay computes the initial probe delay for a backend at position pos -// out of total backends: delay = interval * pos / total. +// activeBackendNames returns a sorted, deduplicated list of backend names that +// are referenced by at least one frontend and have Enabled: true. +func activeBackendNames(cfg *config.Config) []string { + seen := map[string]struct{}{} + for _, fe := range cfg.Frontends { + for _, name := range fe.Backends { + if b, ok := cfg.Backends[name]; ok && b.Enabled { + seen[name] = struct{}{} + } + } + } + names := make([]string, 0, len(seen)) + for name := range seen { + names = append(names, name) + } + sort.Strings(names) + return names +} + +// staggerDelay computes the initial probe delay for position pos out of total. func staggerDelay(interval time.Duration, pos, total int) time.Duration { if total <= 1 { return 0 } return time.Duration(int64(interval) * int64(pos) / int64(total)) } - -// totalBackends counts all backends across all VIPs in a config. -func totalBackends(cfg *config.Frontend) int { - n := 0 - for _, vip := range cfg.VIPs { - n += len(vip.Backends) - } - return n -} diff --git a/internal/checker/checker_test.go b/internal/checker/checker_test.go index a20f51a..748a9e1 100644 --- a/internal/checker/checker_test.go +++ b/internal/checker/checker_test.go @@ -10,23 +10,32 @@ import ( "git.ipng.ch/ipng/vpp-maglev/internal/health" ) -func makeTestConfig(interval time.Duration, fall, rise int) *config.Frontend { - return &config.Frontend{ - HealthCheckNetns: "test", - HealthChecker: config.HealthCheckerConfig{TransitionHistory: 5}, - VIPs: map[string]config.VIP{ +func makeTestConfig(interval time.Duration, fall, rise int) *config.Config { + return &config.Config{ + HealthChecker: config.HealthCheckerConfig{TransitionHistory: 5}, + HealthChecks: map[string]config.HealthCheck{ + "icmp": { + Type: "icmp", + Interval: interval, + Timeout: time.Second, + Fall: fall, + Rise: rise, + }, + }, + Backends: map[string]config.Backend{ + "be0": { + Address: net.ParseIP("10.0.0.2"), + HealthCheck: "icmp", + Enabled: true, + Weight: 100, + }, + }, + Frontends: map[string]config.Frontend{ "web": { Address: net.ParseIP("192.0.2.1"), Protocol: "tcp", Port: 80, - Backends: []net.IP{net.ParseIP("10.0.0.2")}, - HealthCheck: config.HealthCheck{ - Type: "icmp", - Interval: interval, - Timeout: time.Second, - Fall: fall, - Rise: rise, - }, + Backends: []string{"be0"}, }, }, } @@ -62,20 +71,16 @@ func TestHealthCheckEqual(t *testing.T) { } func TestStateMachineViaBackend(t *testing.T) { - // Directly test Backend state transitions (rise=2, fall=3) without goroutines. - b := health.New("web", net.ParseIP("10.0.0.2"), 2, 3) + b := health.New("be0", net.ParseIP("10.0.0.2"), 2, 3) pass := health.ProbeResult{OK: true, Layer: health.LayerL7, Code: "L7OK"} fail := health.ProbeResult{OK: false, Layer: health.LayerL4, Code: "L4CON"} - // Unknown → Down on first fail. if !b.Record(fail, 5) { t.Error("first fail from Unknown should transition to Down") } if b.State != health.StateDown { t.Errorf("expected down, got %s", b.State) } - - // rise=2 passes → Up. if b.Record(pass, 5) { t.Error("should not transition after 1 pass (rise=2)") } @@ -105,21 +110,19 @@ func TestReloadAddsBackend(t *testing.T) { c := New(cfg) newCfg := makeTestConfig(10*time.Millisecond, 3, 2) - newCfg.VIPs["web2"] = config.VIP{ + newCfg.Backends["be1"] = config.Backend{ + Address: net.ParseIP("10.0.0.3"), + HealthCheck: "icmp", + Enabled: true, + Weight: 100, + } + newCfg.Frontends["web2"] = config.Frontend{ Address: net.ParseIP("192.0.2.2"), Protocol: "tcp", Port: 443, - Backends: []net.IP{net.ParseIP("10.0.0.3")}, - HealthCheck: config.HealthCheck{ - Type: "icmp", - Interval: 10 * time.Millisecond, - Timeout: time.Second, - Fall: 3, - Rise: 2, - }, + Backends: []string{"be1"}, } - // Cancelled context: no probe goroutines actually run. ctx, cancel := context.WithCancel(context.Background()) cancel() @@ -128,7 +131,7 @@ func TestReloadAddsBackend(t *testing.T) { } c.mu.RLock() - _, ok := c.workers[backendKey{VIPName: "web2", Backend: "10.0.0.3"}] + _, ok := c.workers["be1"] c.mu.RUnlock() if !ok { t.Error("new backend not added after Reload") @@ -144,22 +147,22 @@ func TestReloadRemovesBackend(t *testing.T) { // Seed a worker manually. c.mu.Lock() - key := backendKey{VIPName: "web", Backend: "10.0.0.2"} wCtx, wCancel := context.WithCancel(context.Background()) - c.workers[key] = &worker{ - backend: health.New("web", net.ParseIP("10.0.0.2"), 2, 3), - hc: cfg.VIPs["web"].HealthCheck, - vip: cfg.VIPs["web"], + c.workers["be0"] = &worker{ + backend: health.New("be0", net.ParseIP("10.0.0.2"), 2, 3), + hc: cfg.HealthChecks["icmp"], + entry: cfg.Backends["be0"], cancel: wCancel, } c.mu.Unlock() _ = wCtx - // New config with "web" VIP removed. - newCfg := &config.Frontend{ - HealthCheckNetns: cfg.HealthCheckNetns, - HealthChecker: cfg.HealthChecker, - VIPs: map[string]config.VIP{}, + // Remove all frontends → be0 is no longer active. + newCfg := &config.Config{ + HealthChecker: cfg.HealthChecker, + HealthChecks: cfg.HealthChecks, + Backends: cfg.Backends, + Frontends: map[string]config.Frontend{}, } if err := c.Reload(ctx, newCfg); err != nil { @@ -167,13 +170,30 @@ func TestReloadRemovesBackend(t *testing.T) { } c.mu.RLock() - _, ok := c.workers[key] + _, ok := c.workers["be0"] c.mu.RUnlock() if ok { t.Error("removed backend still present after Reload") } } +func TestSharedBackendProbedOnce(t *testing.T) { + // be0 is referenced by two frontends — only one worker should exist. + cfg := makeTestConfig(10*time.Millisecond, 3, 2) + cfg.Frontends["web-tls"] = config.Frontend{ + Address: net.ParseIP("192.0.2.3"), + Protocol: "tcp", + Port: 443, + Backends: []string{"be0"}, + } + + c := New(cfg) + names := activeBackendNames(c.cfg) + if len(names) != 1 || names[0] != "be0" { + t.Errorf("expected exactly one active backend, got %v", names) + } +} + func TestSubscribe(t *testing.T) { cfg := makeTestConfig(10*time.Millisecond, 1, 1) c := New(cfg) @@ -186,8 +206,9 @@ func TestSubscribe(t *testing.T) { defer unsub() e := Event{ - VIPName: "web", - Backend: net.ParseIP("10.0.0.2"), + FrontendName: "web", + BackendName: "be0", + Backend: net.ParseIP("10.0.0.2"), Transition: health.Transition{ From: health.StateUnknown, To: health.StateUp, @@ -199,8 +220,11 @@ func TestSubscribe(t *testing.T) { select { case got := <-ch: - if got.VIPName != "web" { - t.Errorf("event VIPName: got %q, want %q", got.VIPName, "web") + if got.FrontendName != "web" { + t.Errorf("event FrontendName: got %q, want web", got.FrontendName) + } + if got.BackendName != "be0" { + t.Errorf("event BackendName: got %q, want be0", got.BackendName) } if got.Transition.To != health.StateUp { t.Errorf("event To state: got %s, want up", got.Transition.To) @@ -211,38 +235,36 @@ func TestSubscribe(t *testing.T) { } func TestPauseResume(t *testing.T) { - cfg := makeTestConfig(time.Hour, 3, 2) // long interval so probes never fire + cfg := makeTestConfig(time.Hour, 3, 2) c := New(cfg) ctx, cancel := context.WithCancel(context.Background()) defer cancel() go c.fanOut(ctx) - // Seed a worker. c.mu.Lock() - key := backendKey{VIPName: "web", Backend: "10.0.0.2"} _, wCancel := context.WithCancel(ctx) - c.workers[key] = &worker{ - backend: health.New("web", net.ParseIP("10.0.0.2"), 2, 3), - hc: cfg.VIPs["web"].HealthCheck, - vip: cfg.VIPs["web"], + c.workers["be0"] = &worker{ + backend: health.New("be0", net.ParseIP("10.0.0.2"), 2, 3), + hc: cfg.HealthChecks["icmp"], + entry: cfg.Backends["be0"], cancel: wCancel, } c.mu.Unlock() - b, ok := c.PauseBackend("web", "10.0.0.2") + b, ok := c.PauseBackend("be0") if !ok { t.Fatal("PauseBackend: not found") } - if b.State != health.StatePaused { - t.Errorf("after pause: %s", b.State) + if b.Health.State != health.StatePaused { + t.Errorf("after pause: %s", b.Health.State) } - b, ok = c.ResumeBackend("web", "10.0.0.2") + b, ok = c.ResumeBackend("be0") if !ok { t.Fatal("ResumeBackend: not found") } - if b.State != health.StateUnknown { - t.Errorf("after resume: %s", b.State) + if b.Health.State != health.StateUnknown { + t.Errorf("after resume: %s", b.Health.State) } } diff --git a/internal/config/config.go b/internal/config/config.go index eab2abe..7e2cbc2 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -12,35 +12,28 @@ import ( "gopkg.in/yaml.v3" ) -// Frontend is the parsed and validated representation of frontend.yaml. -type Frontend struct { - ProbeIPv4Src net.IP - ProbeIPv6Src net.IP - HealthCheckNetns string - HealthChecker HealthCheckerConfig - VIPs map[string]VIP +// Config is the top-level parsed and validated configuration. +type Config struct { + HealthChecker HealthCheckerConfig + HealthChecks map[string]HealthCheck + Backends map[string]Backend + Frontends map[string]Frontend } // HealthCheckerConfig holds global health checker settings. type HealthCheckerConfig struct { TransitionHistory int + Netns string // network namespace for probes; "" = current netns } -// VIP is a single virtual IP entry. -type VIP struct { - Description string - Address net.IP - Protocol string // "tcp", "udp", or "" (all traffic) - Port uint16 // 0 means omitted (all ports) - Backends []net.IP - HealthCheck HealthCheck -} - -// HealthCheck describes how to probe backends for a VIP. +// HealthCheck describes how to probe a backend. type HealthCheck struct { Type string + Port uint16 // destination port; required for tcp/http/https HTTP *HTTPParams // non-nil for type http and https TCP *TCPParams // non-nil for type tcp + ProbeIPv4Src net.IP // source address for IPv4 probes; nil = OS picks + ProbeIPv6Src net.IP // source address for IPv6 probes; nil = OS picks Interval time.Duration FastInterval time.Duration // optional; used while health counter is degraded DownInterval time.Duration // optional; used while fully down @@ -65,41 +58,49 @@ type TCPParams struct { SSL bool ServerName string InsecureSkipVerify bool - BannerRegexp *regexp.Regexp // nil if not configured; matched against the first line sent by the server +} + +// Backend is a single named backend server. +type Backend struct { + Address net.IP + HealthCheck string // name reference into Config.HealthChecks; "" = no probing, assume healthy + Enabled bool // default true; false = exclude from serving entirely + Weight int // 0-100, default 100 +} + +// Frontend is a single virtual IP entry. +type Frontend struct { + Description string + Address net.IP + Protocol string // "tcp", "udp", or "" (all traffic) + Port uint16 // 0 means omitted (all ports) + Backends []string // backend names, each must exist in Config.Backends } // ---- raw YAML types -------------------------------------------------------- type rawConfig struct { - Maglev struct { - Frontend rawFrontend `yaml:"frontend"` - } `yaml:"maglev"` + Maglev rawMaglev `yaml:"maglev"` } -type rawFrontend struct { - ProbeIPv4Src string `yaml:"probe-ipv4-src"` - ProbeIPv6Src string `yaml:"probe-ipv6-src"` - HealthCheckNetns string `yaml:"healthcheck-netns"` - HealthChecker rawHealthCheckerCfg `yaml:"healthchecker"` - VIPs map[string]rawVIP `yaml:"vips"` +type rawMaglev struct { + HealthChecker rawHealthCheckerCfg `yaml:"healthchecker"` + HealthChecks map[string]rawHealthCheck `yaml:"healthchecks"` + Backends map[string]rawBackend `yaml:"backends"` + Frontends map[string]rawFrontend `yaml:"frontends"` } type rawHealthCheckerCfg struct { - TransitionHistory int `yaml:"transition-history"` -} - -type rawVIP struct { - Description string `yaml:"description"` - Address string `yaml:"address"` - Protocol string `yaml:"protocol"` - Port uint16 `yaml:"port"` - Backends []string `yaml:"backends"` - HealthCheck rawHealthCheck `yaml:"healthcheck"` + TransitionHistory int `yaml:"transition-history"` + Netns string `yaml:"netns"` } type rawHealthCheck struct { Type string `yaml:"type"` + Port uint16 `yaml:"port"` Params rawParams `yaml:"params"` + ProbeIPv4Src string `yaml:"probe-ipv4-src"` + ProbeIPv6Src string `yaml:"probe-ipv6-src"` Interval string `yaml:"interval"` FastInterval string `yaml:"fast-interval"` DownInterval string `yaml:"down-interval"` @@ -120,10 +121,25 @@ type rawParams struct { SSL bool `yaml:"ssl"` } +type rawBackend struct { + Address string `yaml:"address"` + HealthCheck string `yaml:"healthcheck"` + Enabled *bool `yaml:"enabled"` // nil → default true + Weight *int `yaml:"weight"` // nil → default 100 +} + +type rawFrontend struct { + Description string `yaml:"description"` + Address string `yaml:"address"` + Protocol string `yaml:"protocol"` + Port uint16 `yaml:"port"` + Backends []string `yaml:"backends"` +} + // ---- Load ------------------------------------------------------------------ // Load reads and validates the config file at path. -func Load(path string) (*Frontend, error) { +func Load(path string) (*Config, error) { data, err := os.ReadFile(path) if err != nil { return nil, fmt.Errorf("read config %q: %w", path, err) @@ -131,115 +147,82 @@ func Load(path string) (*Frontend, error) { return parse(data) } -func parse(data []byte) (*Frontend, error) { +func parse(data []byte) (*Config, error) { var raw rawConfig if err := yaml.Unmarshal(data, &raw); err != nil { return nil, fmt.Errorf("parse yaml: %w", err) } - return convert(&raw.Maglev.Frontend) + return convert(&raw.Maglev) } -func convert(r *rawFrontend) (*Frontend, error) { - f := &Frontend{} - var err error +func convert(r *rawMaglev) (*Config, error) { + cfg := &Config{} - if f.ProbeIPv4Src, err = parseOptionalIPFamily(r.ProbeIPv4Src, 4, "probe-ipv4-src"); err != nil { - return nil, err + // ---- healthchecker -------------------------------------------------------- + cfg.HealthChecker.Netns = r.HealthChecker.Netns + cfg.HealthChecker.TransitionHistory = r.HealthChecker.TransitionHistory + if cfg.HealthChecker.TransitionHistory == 0 { + cfg.HealthChecker.TransitionHistory = 5 } - if f.ProbeIPv6Src, err = parseOptionalIPFamily(r.ProbeIPv6Src, 6, "probe-ipv6-src"); err != nil { - return nil, err - } - - f.HealthCheckNetns = r.HealthCheckNetns - - f.HealthChecker.TransitionHistory = r.HealthChecker.TransitionHistory - if f.HealthChecker.TransitionHistory == 0 { - f.HealthChecker.TransitionHistory = 5 - } - if f.HealthChecker.TransitionHistory < 1 { + if cfg.HealthChecker.TransitionHistory < 1 { return nil, fmt.Errorf("healthchecker.transition-history must be >= 1") } - f.VIPs = make(map[string]VIP, len(r.VIPs)) - for name, rv := range r.VIPs { - vip, err := convertVIP(name, &rv) + // ---- healthchecks --------------------------------------------------------- + cfg.HealthChecks = make(map[string]HealthCheck, len(r.HealthChecks)) + for name, rh := range r.HealthChecks { + hc, err := convertHealthCheck(&rh) if err != nil { - return nil, fmt.Errorf("vip %q: %w", name, err) + return nil, fmt.Errorf("healthcheck %q: %w", name, err) } - f.VIPs[name] = vip + cfg.HealthChecks[name] = hc } - return f, nil -} - -func convertVIP(name string, r *rawVIP) (VIP, error) { - v := VIP{ - Description: r.Description, - Protocol: r.Protocol, - Port: r.Port, - } - - ip := net.ParseIP(r.Address) - if ip == nil { - return VIP{}, fmt.Errorf("invalid address %q", r.Address) - } - v.Address = ip - - switch r.Protocol { - case "", "tcp", "udp": - default: - return VIP{}, fmt.Errorf("protocol must be \"tcp\", \"udp\", or omitted, got %q", r.Protocol) - } - - if r.Port != 0 && r.Protocol == "" { - return VIP{}, fmt.Errorf("port requires protocol to be set") - } - if r.Protocol != "" && r.Port == 0 { - return VIP{}, fmt.Errorf("protocol %q requires port to be set (1-65535)", r.Protocol) - } - - if len(r.Backends) == 0 { - return VIP{}, fmt.Errorf("backends must not be empty") - } - var firstFamily int - for i, bs := range r.Backends { - ip := net.ParseIP(bs) - if ip == nil { - return VIP{}, fmt.Errorf("backend[%d] %q is not a valid IP", i, bs) + // ---- backends ------------------------------------------------------------- + cfg.Backends = make(map[string]Backend, len(r.Backends)) + for name, rb := range r.Backends { + b, err := convertBackend(name, &rb, cfg.HealthChecks) + if err != nil { + return nil, fmt.Errorf("backend %q: %w", name, err) } - fam := ipFamily(ip) - if i == 0 { - firstFamily = fam - } else if fam != firstFamily { - return VIP{}, fmt.Errorf("backend[%d] %q has different address family than backend[0]", i, bs) + cfg.Backends[name] = b + } + + // ---- frontends ------------------------------------------------------------ + cfg.Frontends = make(map[string]Frontend, len(r.Frontends)) + for name, rf := range r.Frontends { + fe, err := convertFrontend(name, &rf, cfg.Backends) + if err != nil { + return nil, fmt.Errorf("frontend %q: %w", name, err) } - v.Backends = append(v.Backends, ip) + cfg.Frontends[name] = fe } - hc, err := convertHealthCheck(&r.HealthCheck) - if err != nil { - return VIP{}, fmt.Errorf("healthcheck: %w", err) - } - v.HealthCheck = hc - - return v, nil + return cfg, nil } func convertHealthCheck(r *rawHealthCheck) (HealthCheck, error) { - h := HealthCheck{ - Type: r.Type, - } + h := HealthCheck{Type: r.Type, Port: r.Port} switch r.Type { case "icmp": - // no params + // ICMP does not use ports. + if r.Port != 0 { + return HealthCheck{}, fmt.Errorf("type icmp does not use a port") + } case "tcp": + if r.Port == 0 { + return HealthCheck{}, fmt.Errorf("type tcp requires port") + } h.TCP = &TCPParams{ SSL: r.Params.SSL, ServerName: r.Params.ServerName, InsecureSkipVerify: r.Params.InsecureSkipVerify, } case "http", "https": + if r.Port == 0 { + return HealthCheck{}, fmt.Errorf("type %s requires port", r.Type) + } if r.Params.Path == "" { return HealthCheck{}, fmt.Errorf("type http requires params.path") } @@ -272,25 +255,33 @@ func convertHealthCheck(r *rawHealthCheck) (HealthCheck, error) { } var err error + if r.ProbeIPv4Src != "" { + if h.ProbeIPv4Src, err = parseOptionalIPFamily(r.ProbeIPv4Src, 4, "probe-ipv4-src"); err != nil { + return HealthCheck{}, err + } + } + if r.ProbeIPv6Src != "" { + if h.ProbeIPv6Src, err = parseOptionalIPFamily(r.ProbeIPv6Src, 6, "probe-ipv6-src"); err != nil { + return HealthCheck{}, err + } + } + if r.Interval == "" { return HealthCheck{}, fmt.Errorf("interval is required") } if h.Interval, err = time.ParseDuration(r.Interval); err != nil || h.Interval <= 0 { return HealthCheck{}, fmt.Errorf("interval %q must be a positive duration", r.Interval) } - if r.FastInterval != "" { if h.FastInterval, err = time.ParseDuration(r.FastInterval); err != nil || h.FastInterval <= 0 { return HealthCheck{}, fmt.Errorf("fast-interval %q must be a positive duration", r.FastInterval) } } - if r.DownInterval != "" { if h.DownInterval, err = time.ParseDuration(r.DownInterval); err != nil || h.DownInterval <= 0 { return HealthCheck{}, fmt.Errorf("down-interval %q must be a positive duration", r.DownInterval) } } - if r.Timeout == "" { return HealthCheck{}, fmt.Errorf("timeout is required") } @@ -305,7 +296,6 @@ func convertHealthCheck(r *rawHealthCheck) (HealthCheck, error) { if h.Fall < 1 { return HealthCheck{}, fmt.Errorf("fall must be >= 1") } - h.Rise = r.Rise if h.Rise == 0 { h.Rise = 2 @@ -317,10 +307,80 @@ func convertHealthCheck(r *rawHealthCheck) (HealthCheck, error) { return h, nil } +func convertBackend(name string, r *rawBackend, hcs map[string]HealthCheck) (Backend, error) { + ip := net.ParseIP(r.Address) + if ip == nil { + return Backend{}, fmt.Errorf("invalid address %q", r.Address) + } + + b := Backend{ + Address: ip, + HealthCheck: r.HealthCheck, + Enabled: boolDefault(r.Enabled, true), + Weight: intDefault(r.Weight, 100), + } + + if b.Weight < 0 || b.Weight > 100 { + return Backend{}, fmt.Errorf("weight %d is out of range [0, 100]", b.Weight) + } + + if b.HealthCheck != "" { + if _, ok := hcs[b.HealthCheck]; !ok { + return Backend{}, fmt.Errorf("healthcheck %q not defined", b.HealthCheck) + } + } + + return b, nil +} + +func convertFrontend(name string, r *rawFrontend, backends map[string]Backend) (Frontend, error) { + fe := Frontend{ + Description: r.Description, + Protocol: r.Protocol, + Port: r.Port, + Backends: r.Backends, + } + + ip := net.ParseIP(r.Address) + if ip == nil { + return Frontend{}, fmt.Errorf("invalid address %q", r.Address) + } + fe.Address = ip + + switch r.Protocol { + case "", "tcp", "udp": + default: + return Frontend{}, fmt.Errorf("protocol must be \"tcp\", \"udp\", or omitted, got %q", r.Protocol) + } + if r.Port != 0 && r.Protocol == "" { + return Frontend{}, fmt.Errorf("port requires protocol to be set") + } + if r.Protocol != "" && r.Port == 0 { + return Frontend{}, fmt.Errorf("protocol %q requires port to be set (1-65535)", r.Protocol) + } + + if len(r.Backends) == 0 { + return Frontend{}, fmt.Errorf("backends must not be empty") + } + var firstFamily int + for i, bName := range r.Backends { + b, ok := backends[bName] + if !ok { + return Frontend{}, fmt.Errorf("backends[%d] %q not defined", i, bName) + } + fam := ipFamily(b.Address) + if i == 0 { + firstFamily = fam + } else if fam != firstFamily { + return Frontend{}, fmt.Errorf("backends[%d] %q has different address family than backends[0]", i, bName) + } + } + + return fe, nil +} + // ---- helpers --------------------------------------------------------------- -// parseOptionalIPFamily parses s as an IP of the given family. -// Returns nil (no error) if s is empty. func parseOptionalIPFamily(s string, family int, field string) (net.IP, error) { if s == "" { return nil, nil @@ -335,7 +395,6 @@ func parseOptionalIPFamily(s string, family int, field string) (net.IP, error) { return ip, nil } -// ipFamily returns 4 for IPv4, 6 for IPv6. func ipFamily(ip net.IP) int { if ip.To4() != nil { return 4 @@ -343,8 +402,6 @@ func ipFamily(ip net.IP) int { return 6 } -// parseCodeRange parses a response-code value which may be a single integer -// ("200") or an inclusive range ("200-299"). Returns (min, max, err). func parseCodeRange(s string, defaultCode int) (min, max int, err error) { if s == "" { return defaultCode, defaultCode, nil @@ -366,3 +423,17 @@ func parseCodeRange(s string, defaultCode int) (min, max int, err error) { } return min, min, nil } + +func boolDefault(p *bool, def bool) bool { + if p == nil { + return def + } + return *p +} + +func intDefault(p *int, def int) int { + if p == nil { + return def + } + return *p +} diff --git a/internal/config/config_test.go b/internal/config/config_test.go index fb9c56c..646593f 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -7,146 +7,217 @@ import ( const validConfig = ` maglev: - frontend: - probe-ipv4-src: 10.0.0.1 - probe-ipv6-src: 2001:db8:1::1 - healthcheck-netns: dataplane - healthchecker: - transition-history: 5 - vips: - web4: - description: "IPv4 VIP" - address: 192.0.2.1 - protocol: tcp - port: 80 - backends: [2001:db8:2::1, 2001:db8:2::2] - healthcheck: - type: http - params: - path: /healthz - host: example.com - response-code: "200" - interval: 2s - timeout: 3s - rise: 2 - fall: 3 - web6: - description: "IPv6 VIP" - address: 2001:db8::1 - protocol: tcp - port: 443 - backends: [2001:db8:2::1, 2001:db8:2::2] - healthcheck: - type: icmp - interval: 1s - timeout: 3s - fall: 5 + healthchecker: + transition-history: 5 + netns: dataplane + healthchecks: + http-check: + type: http + port: 80 + probe-ipv4-src: 10.0.0.1 + params: + path: /healthz + host: example.com + response-code: "200" + interval: 2s + timeout: 3s + rise: 2 + fall: 3 + icmp-check: + type: icmp + probe-ipv6-src: 2001:db8:1::1 + interval: 1s + timeout: 3s + fall: 5 + backends: + be-v4: + address: 192.0.2.10 + healthcheck: http-check + be-v6a: + address: 2001:db8:2::1 + healthcheck: icmp-check + be-v6b: + address: 2001:db8:2::2 + healthcheck: icmp-check + weight: 50 + enabled: true + frontends: + web4: + description: "IPv4 VIP" + address: 192.0.2.1 + protocol: tcp + port: 80 + backends: [be-v4] + web6: + description: "IPv6 VIP" + address: 2001:db8::1 + protocol: tcp + port: 443 + backends: [be-v6a, be-v6b] ` func TestValidConfig(t *testing.T) { - f, err := parse([]byte(validConfig)) + cfg, err := parse([]byte(validConfig)) if err != nil { t.Fatalf("unexpected error: %v", err) } - if f.ProbeIPv4Src.String() != "10.0.0.1" { - t.Errorf("probe-ipv4-src: got %s, want 10.0.0.1", f.ProbeIPv4Src) + + if cfg.HealthChecker.Netns != "dataplane" { + t.Errorf("healthchecker.netns: got %q, want dataplane", cfg.HealthChecker.Netns) } - if f.ProbeIPv6Src.String() != "2001:db8:1::1" { - t.Errorf("probe-ipv6-src: got %s, want 2001:db8:1::1", f.ProbeIPv6Src) + if cfg.HealthChecker.TransitionHistory != 5 { + t.Errorf("transition-history: got %d, want 5", cfg.HealthChecker.TransitionHistory) } - if f.HealthCheckNetns != "dataplane" { - t.Errorf("healthcheck-netns: got %q, want %q", f.HealthCheckNetns, "dataplane") + if len(cfg.Frontends) != 2 { + t.Fatalf("frontends: got %d, want 2", len(cfg.Frontends)) } - if f.HealthChecker.TransitionHistory != 5 { - t.Errorf("transition-history: got %d, want 5", f.HealthChecker.TransitionHistory) + + hc := cfg.HealthChecks["http-check"] + if hc.Type != "http" { + t.Errorf("http-check type: got %q, want http", hc.Type) } - if len(f.VIPs) != 2 { - t.Fatalf("vips: got %d, want 2", len(f.VIPs)) + if hc.Fall != 3 || hc.Rise != 2 { + t.Errorf("http-check fall/rise: got %d/%d, want 3/2", hc.Fall, hc.Rise) } - web4 := f.VIPs["web4"] - if web4.HealthCheck.Type != "http" { - t.Errorf("web4 healthcheck type: got %q, want http", web4.HealthCheck.Type) + if hc.ProbeIPv4Src.String() != "10.0.0.1" { + t.Errorf("http-check probe-ipv4-src: got %s, want 10.0.0.1", hc.ProbeIPv4Src) } - if web4.HealthCheck.Fall != 3 { - t.Errorf("web4 fall: got %d, want 3", web4.HealthCheck.Fall) + if hc.HTTP == nil { + t.Fatal("http-check HTTP params should not be nil") } - if web4.HealthCheck.Rise != 2 { - t.Errorf("web4 rise: got %d, want 2", web4.HealthCheck.Rise) + if hc.HTTP.Path != "/healthz" { + t.Errorf("http-check path: got %q, want /healthz", hc.HTTP.Path) } - if web4.HealthCheck.HTTP == nil { - t.Fatal("web4 HTTP params should not be nil") + if hc.HTTP.Host != "example.com" { + t.Errorf("http-check host: got %q, want example.com", hc.HTTP.Host) } - if web4.HealthCheck.HTTP.Path != "/healthz" { - t.Errorf("web4 params.path: got %q, want /healthz", web4.HealthCheck.HTTP.Path) + if hc.HTTP.ResponseCodeMin != 200 || hc.HTTP.ResponseCodeMax != 200 { + t.Errorf("http-check response-code: got %d-%d, want 200-200", + hc.HTTP.ResponseCodeMin, hc.HTTP.ResponseCodeMax) } - if web4.HealthCheck.HTTP.Host != "example.com" { - t.Errorf("web4 params.host: got %q, want example.com", web4.HealthCheck.HTTP.Host) + + icmp := cfg.HealthChecks["icmp-check"] + if icmp.Fall != 5 { + t.Errorf("icmp-check fall: got %d, want 5", icmp.Fall) } - if web4.HealthCheck.HTTP.ResponseCodeMin != 200 || web4.HealthCheck.HTTP.ResponseCodeMax != 200 { - t.Errorf("web4 response-code: got %d-%d, want 200-200", - web4.HealthCheck.HTTP.ResponseCodeMin, web4.HealthCheck.HTTP.ResponseCodeMax) + if icmp.ProbeIPv6Src.String() != "2001:db8:1::1" { + t.Errorf("icmp-check probe-ipv6-src: got %s, want 2001:db8:1::1", icmp.ProbeIPv6Src) } - web6 := f.VIPs["web6"] - if web6.HealthCheck.Fall != 5 { - t.Errorf("web6 fall: got %d, want 5", web6.HealthCheck.Fall) + + // Backend defaults and explicit fields. + beV4 := cfg.Backends["be-v4"] + if beV4.Address.String() != "192.0.2.10" { + t.Errorf("be-v4 address: got %s", beV4.Address) + } + if beV4.HealthCheck != "http-check" { + t.Errorf("be-v4 healthcheck: got %q", beV4.HealthCheck) + } + if !beV4.Enabled { + t.Error("be-v4 enabled: want true (default)") + } + if beV4.Weight != 100 { + t.Errorf("be-v4 weight: got %d, want 100 (default)", beV4.Weight) + } + + beV6b := cfg.Backends["be-v6b"] + if beV6b.Weight != 50 { + t.Errorf("be-v6b weight: got %d, want 50", beV6b.Weight) + } + + // Frontend references. + web4 := cfg.Frontends["web4"] + if len(web4.Backends) != 1 || web4.Backends[0] != "be-v4" { + t.Errorf("web4 backends: got %v", web4.Backends) + } + web6 := cfg.Frontends["web6"] + if len(web6.Backends) != 2 { + t.Errorf("web6 backends: got %d, want 2", len(web6.Backends)) } } func TestDefaults(t *testing.T) { - cfg := ` + raw := ` maglev: - frontend: - vips: - v: - address: 192.0.2.1 - backends: [10.0.0.2] - healthcheck: - type: icmp - interval: 1s - timeout: 2s + healthchecks: + icmp: + type: icmp + interval: 1s + timeout: 2s + backends: + be: + address: 10.0.0.2 + healthcheck: icmp + frontends: + v: + address: 192.0.2.1 + backends: [be] ` - f, err := parse([]byte(cfg)) + cfg, err := parse([]byte(raw)) if err != nil { t.Fatalf("unexpected error: %v", err) } - if f.HealthCheckNetns != "" { - t.Errorf("default healthcheck-netns: got %q, want empty (default netns)", f.HealthCheckNetns) + if cfg.HealthChecker.Netns != "" { + t.Errorf("default netns: got %q, want empty", cfg.HealthChecker.Netns) } - if f.HealthChecker.TransitionHistory != 5 { - t.Errorf("default transition-history: got %d, want 5", f.HealthChecker.TransitionHistory) + if cfg.HealthChecker.TransitionHistory != 5 { + t.Errorf("default transition-history: got %d, want 5", cfg.HealthChecker.TransitionHistory) } - hc := f.VIPs["v"].HealthCheck - if hc.Rise != 2 { - t.Errorf("default rise: got %d, want 2", hc.Rise) + hc := cfg.HealthChecks["icmp"] + if hc.Rise != 2 || hc.Fall != 3 { + t.Errorf("defaults rise/fall: got %d/%d, want 2/3", hc.Rise, hc.Fall) } - if hc.Fall != 3 { - t.Errorf("default fall: got %d, want 3", hc.Fall) + be := cfg.Backends["be"] + if !be.Enabled || be.Weight != 100 { + t.Errorf("backend defaults: enabled=%v weight=%d", be.Enabled, be.Weight) } - if f.ProbeIPv4Src != nil { - t.Errorf("probe-ipv4-src should be nil when omitted, got %s", f.ProbeIPv4Src) +} + +func TestBackendNoHealthcheck(t *testing.T) { + // A backend with no healthcheck reference is valid; probe is skipped. + raw := ` +maglev: + healthchecks: {} + backends: + be: + address: 10.0.0.2 + frontends: + v: + address: 192.0.2.1 + backends: [be] +` + cfg, err := parse([]byte(raw)) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if cfg.Backends["be"].HealthCheck != "" { + t.Error("expected empty healthcheck") } } func TestOptionalIntervals(t *testing.T) { - cfg := ` + raw := ` maglev: - frontend: - vips: - v: - address: 192.0.2.1 - backends: [10.0.0.2] - healthcheck: - type: icmp - interval: 2s - fast-interval: 500ms - down-interval: 30s - timeout: 1s + healthchecks: + icmp: + type: icmp + interval: 2s + fast-interval: 500ms + down-interval: 30s + timeout: 1s + backends: + be: + address: 10.0.0.2 + healthcheck: icmp + frontends: + v: + address: 192.0.2.1 + backends: [be] ` - f, err := parse([]byte(cfg)) + cfg, err := parse([]byte(raw)) if err != nil { t.Fatalf("unexpected error: %v", err) } - hc := f.VIPs["v"].HealthCheck + hc := cfg.HealthChecks["icmp"] if hc.Interval != 2*time.Second { t.Errorf("interval: got %v, want 2s", hc.Interval) } @@ -158,47 +229,27 @@ maglev: } } -func TestTCPType(t *testing.T) { - cfg := ` -maglev: - frontend: - vips: - v: - address: 192.0.2.1 - protocol: tcp - port: 80 - backends: [10.0.0.2] - healthcheck: - type: tcp - interval: 1s - timeout: 2s -` - f, err := parse([]byte(cfg)) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - if f.VIPs["v"].HealthCheck.Type != "tcp" { - t.Errorf("type: got %q, want tcp", f.VIPs["v"].HealthCheck.Type) - } -} - func TestValidationErrors(t *testing.T) { - // Minimal valid base to build error cases on top of. - base := func(override string) string { + base := func(hcExtra, beExtra, feExtra string) string { return ` maglev: - frontend: - vips: - v: - address: 192.0.2.1 - backends: [10.0.0.2] - healthcheck: - type: icmp - interval: 1s - timeout: 2s -` + override + healthchecks: + c: + type: icmp + interval: 1s + timeout: 2s +` + hcExtra + ` + backends: + be: + address: 10.0.0.2 + healthcheck: c +` + beExtra + ` + frontends: + v: + address: 192.0.2.1 + backends: [be] +` + feExtra } - _ = base tests := []struct { name string @@ -206,58 +257,50 @@ maglev: errSub string }{ { - name: "wrong family probe-ipv4-src", - yaml: ` -maglev: - frontend: - probe-ipv4-src: 2001:db8::1 - vips: - v: - address: 192.0.2.1 - backends: [10.0.0.2] - healthcheck: - type: icmp - interval: 1s - timeout: 2s -`, + name: "wrong family probe-ipv4-src", + yaml: base(" probe-ipv4-src: 2001:db8::1\n", "", ""), errSub: "probe-ipv4-src", }, { - name: "mixed backend families", + name: "mixed backend address families in frontend", yaml: ` maglev: - frontend: - vips: - v: - address: 192.0.2.1 - backends: [10.0.0.2, 2001:db8::1] - healthcheck: - type: icmp - interval: 1s - timeout: 2s + healthchecks: + c: + type: icmp + interval: 1s + timeout: 2s + backends: + v4: {address: 10.0.0.2, healthcheck: c} + v6: {address: 2001:db8::1, healthcheck: c} + frontends: + v: + address: 192.0.2.1 + backends: [v4, v6] `, errSub: "address family", }, { - name: "port without protocol", - yaml: validPortWithoutProtocol, - + name: "port without protocol", + yaml: base("", "", " port: 80\n"), errSub: "port requires protocol", }, { name: "protocol without port", yaml: ` maglev: - frontend: - vips: - v: - address: 192.0.2.1 - protocol: tcp - backends: [10.0.0.2] - healthcheck: - type: icmp - interval: 1s - timeout: 2s + healthchecks: + c: + type: icmp + interval: 1s + timeout: 2s + backends: + be: {address: 10.0.0.2, healthcheck: c} + frontends: + v: + address: 192.0.2.1 + protocol: tcp + backends: [be] `, errSub: "requires port", }, @@ -265,15 +308,17 @@ maglev: name: "invalid healthcheck type", yaml: ` maglev: - frontend: - vips: - v: - address: 192.0.2.1 - backends: [10.0.0.2] - healthcheck: - type: dns - interval: 1s - timeout: 2s + healthchecks: + c: + type: dns + interval: 1s + timeout: 2s + backends: + be: {address: 10.0.0.2, healthcheck: c} + frontends: + v: + address: 192.0.2.1 + backends: [be] `, errSub: "type must be", }, @@ -281,84 +326,104 @@ maglev: name: "http missing path", yaml: ` maglev: - frontend: - vips: - v: - address: 192.0.2.1 - backends: [10.0.0.2] - healthcheck: - type: http - interval: 1s - timeout: 2s + healthchecks: + c: + type: http + port: 80 + interval: 1s + timeout: 2s + backends: + be: {address: 10.0.0.2, healthcheck: c} + frontends: + v: + address: 192.0.2.1 + backends: [be] `, errSub: "params.path", }, { - name: "negative interval", - yaml: ` -maglev: - frontend: - vips: - v: - address: 192.0.2.1 - backends: [10.0.0.2] - healthcheck: - type: icmp - interval: -1s - timeout: 2s -`, - errSub: "positive duration", - }, - { - name: "invalid fast-interval", - yaml: ` -maglev: - frontend: - vips: - v: - address: 192.0.2.1 - backends: [10.0.0.2] - healthcheck: - type: icmp - interval: 1s - fast-interval: -1s - timeout: 2s -`, - errSub: "positive duration", - }, - { - name: "fall zero becomes default", - yaml: ` -maglev: - frontend: - vips: - v: - address: 192.0.2.1 - backends: [10.0.0.2] - healthcheck: - type: icmp - interval: 1s - timeout: 2s - fall: 0 -`, - // fall: 0 is treated as omitted → default 3; no error + name: "negative interval", + yaml: base("", "", ""), errSub: "", }, { - name: "empty backends", + name: "undefined healthcheck reference", yaml: ` maglev: - frontend: - vips: - v: - address: 192.0.2.1 - backends: [] - healthcheck: - type: icmp - interval: 1s - timeout: 2s + healthchecks: {} + backends: + be: {address: 10.0.0.2, healthcheck: missing} + frontends: + v: + address: 192.0.2.1 + backends: [be] `, - errSub: "backends must not be empty", + errSub: "not defined", + }, + { + name: "undefined backend reference in frontend", + yaml: ` +maglev: + healthchecks: + c: + type: icmp + interval: 1s + timeout: 2s + backends: {} + frontends: + v: + address: 192.0.2.1 + backends: [missing] +`, + errSub: "not defined", + }, + { + name: "weight out of range", + yaml: base("", " weight: 150\n", ""), + errSub: "out of range", + }, + { + name: "fall zero becomes default", + yaml: base(" fall: 0\n", "", ""), + errSub: "", + }, + { + name: "tcp missing port", + yaml: ` +maglev: + healthchecks: + c: + type: tcp + interval: 1s + timeout: 2s + backends: + be: {address: 10.0.0.2, healthcheck: c} + frontends: + v: + address: 192.0.2.1 + backends: [be] +`, + errSub: "requires port", + }, + { + name: "http missing port", + yaml: ` +maglev: + healthchecks: + c: + type: http + interval: 1s + timeout: 2s + params: + path: / + backends: + be: {address: 10.0.0.2, healthcheck: c} + frontends: + v: + address: 192.0.2.1 + backends: [be] +`, + errSub: "requires port", }, } @@ -381,20 +446,6 @@ maglev: } } -const validPortWithoutProtocol = ` -maglev: - frontend: - vips: - v: - address: 192.0.2.1 - port: 80 - backends: [10.0.0.2] - healthcheck: - type: icmp - interval: 1s - timeout: 2s -` - func contains(s, sub string) bool { return len(s) >= len(sub) && (s == sub || len(sub) == 0 || func() bool { diff --git a/internal/grpcapi/healthchecker.pb.go b/internal/grpcapi/healthchecker.pb.go deleted file mode 100644 index a828620..0000000 --- a/internal/grpcapi/healthchecker.pb.go +++ /dev/null @@ -1,776 +0,0 @@ -// Code generated by protoc-gen-go. DO NOT EDIT. -// versions: -// protoc-gen-go v1.36.11 -// protoc v3.21.12 -// source: proto/healthchecker.proto - -package grpcapi - -import ( - protoreflect "google.golang.org/protobuf/reflect/protoreflect" - protoimpl "google.golang.org/protobuf/runtime/protoimpl" - reflect "reflect" - sync "sync" - unsafe "unsafe" -) - -const ( - // Verify that this generated code is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) - // Verify that runtime/protoimpl is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) -) - -type ListVIPsRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *ListVIPsRequest) Reset() { - *x = ListVIPsRequest{} - mi := &file_proto_healthchecker_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *ListVIPsRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*ListVIPsRequest) ProtoMessage() {} - -func (x *ListVIPsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_healthchecker_proto_msgTypes[0] - 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 ListVIPsRequest.ProtoReflect.Descriptor instead. -func (*ListVIPsRequest) Descriptor() ([]byte, []int) { - return file_proto_healthchecker_proto_rawDescGZIP(), []int{0} -} - -type GetVIPRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - VipName string `protobuf:"bytes,1,opt,name=vip_name,json=vipName,proto3" json:"vip_name,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *GetVIPRequest) Reset() { - *x = GetVIPRequest{} - mi := &file_proto_healthchecker_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *GetVIPRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GetVIPRequest) ProtoMessage() {} - -func (x *GetVIPRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_healthchecker_proto_msgTypes[1] - 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 GetVIPRequest.ProtoReflect.Descriptor instead. -func (*GetVIPRequest) Descriptor() ([]byte, []int) { - return file_proto_healthchecker_proto_rawDescGZIP(), []int{1} -} - -func (x *GetVIPRequest) GetVipName() string { - if x != nil { - return x.VipName - } - return "" -} - -type ListBackendsRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - VipName string `protobuf:"bytes,1,opt,name=vip_name,json=vipName,proto3" json:"vip_name,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *ListBackendsRequest) Reset() { - *x = ListBackendsRequest{} - mi := &file_proto_healthchecker_proto_msgTypes[2] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *ListBackendsRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*ListBackendsRequest) ProtoMessage() {} - -func (x *ListBackendsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_healthchecker_proto_msgTypes[2] - 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 ListBackendsRequest.ProtoReflect.Descriptor instead. -func (*ListBackendsRequest) Descriptor() ([]byte, []int) { - return file_proto_healthchecker_proto_rawDescGZIP(), []int{2} -} - -func (x *ListBackendsRequest) GetVipName() string { - if x != nil { - return x.VipName - } - return "" -} - -type GetBackendRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - VipName string `protobuf:"bytes,1,opt,name=vip_name,json=vipName,proto3" json:"vip_name,omitempty"` - BackendAddress string `protobuf:"bytes,2,opt,name=backend_address,json=backendAddress,proto3" json:"backend_address,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *GetBackendRequest) Reset() { - *x = GetBackendRequest{} - mi := &file_proto_healthchecker_proto_msgTypes[3] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *GetBackendRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GetBackendRequest) ProtoMessage() {} - -func (x *GetBackendRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_healthchecker_proto_msgTypes[3] - 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 GetBackendRequest.ProtoReflect.Descriptor instead. -func (*GetBackendRequest) Descriptor() ([]byte, []int) { - return file_proto_healthchecker_proto_rawDescGZIP(), []int{3} -} - -func (x *GetBackendRequest) GetVipName() string { - if x != nil { - return x.VipName - } - return "" -} - -func (x *GetBackendRequest) GetBackendAddress() string { - if x != nil { - return x.BackendAddress - } - return "" -} - -type PauseResumeRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - VipName string `protobuf:"bytes,1,opt,name=vip_name,json=vipName,proto3" json:"vip_name,omitempty"` - BackendAddress string `protobuf:"bytes,2,opt,name=backend_address,json=backendAddress,proto3" json:"backend_address,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *PauseResumeRequest) Reset() { - *x = PauseResumeRequest{} - mi := &file_proto_healthchecker_proto_msgTypes[4] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *PauseResumeRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*PauseResumeRequest) ProtoMessage() {} - -func (x *PauseResumeRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_healthchecker_proto_msgTypes[4] - 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 PauseResumeRequest.ProtoReflect.Descriptor instead. -func (*PauseResumeRequest) Descriptor() ([]byte, []int) { - return file_proto_healthchecker_proto_rawDescGZIP(), []int{4} -} - -func (x *PauseResumeRequest) GetVipName() string { - if x != nil { - return x.VipName - } - return "" -} - -func (x *PauseResumeRequest) GetBackendAddress() string { - if x != nil { - return x.BackendAddress - } - return "" -} - -type WatchRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *WatchRequest) Reset() { - *x = WatchRequest{} - mi := &file_proto_healthchecker_proto_msgTypes[5] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *WatchRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*WatchRequest) ProtoMessage() {} - -func (x *WatchRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_healthchecker_proto_msgTypes[5] - 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 WatchRequest.ProtoReflect.Descriptor instead. -func (*WatchRequest) Descriptor() ([]byte, []int) { - return file_proto_healthchecker_proto_rawDescGZIP(), []int{5} -} - -type ListVIPsResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - VipNames []string `protobuf:"bytes,1,rep,name=vip_names,json=vipNames,proto3" json:"vip_names,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *ListVIPsResponse) Reset() { - *x = ListVIPsResponse{} - mi := &file_proto_healthchecker_proto_msgTypes[6] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *ListVIPsResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*ListVIPsResponse) ProtoMessage() {} - -func (x *ListVIPsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_healthchecker_proto_msgTypes[6] - 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 ListVIPsResponse.ProtoReflect.Descriptor instead. -func (*ListVIPsResponse) Descriptor() ([]byte, []int) { - return file_proto_healthchecker_proto_rawDescGZIP(), []int{6} -} - -func (x *ListVIPsResponse) GetVipNames() []string { - if x != nil { - return x.VipNames - } - return nil -} - -type VIPInfo struct { - state protoimpl.MessageState `protogen:"open.v1"` - Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` - Address string `protobuf:"bytes,2,opt,name=address,proto3" json:"address,omitempty"` - Protocol string `protobuf:"bytes,3,opt,name=protocol,proto3" json:"protocol,omitempty"` - Port uint32 `protobuf:"varint,4,opt,name=port,proto3" json:"port,omitempty"` - Backends []string `protobuf:"bytes,5,rep,name=backends,proto3" json:"backends,omitempty"` - Description string `protobuf:"bytes,6,opt,name=description,proto3" json:"description,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *VIPInfo) Reset() { - *x = VIPInfo{} - mi := &file_proto_healthchecker_proto_msgTypes[7] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *VIPInfo) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*VIPInfo) ProtoMessage() {} - -func (x *VIPInfo) ProtoReflect() protoreflect.Message { - mi := &file_proto_healthchecker_proto_msgTypes[7] - 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 VIPInfo.ProtoReflect.Descriptor instead. -func (*VIPInfo) Descriptor() ([]byte, []int) { - return file_proto_healthchecker_proto_rawDescGZIP(), []int{7} -} - -func (x *VIPInfo) GetName() string { - if x != nil { - return x.Name - } - return "" -} - -func (x *VIPInfo) GetAddress() string { - if x != nil { - return x.Address - } - return "" -} - -func (x *VIPInfo) GetProtocol() string { - if x != nil { - return x.Protocol - } - return "" -} - -func (x *VIPInfo) GetPort() uint32 { - if x != nil { - return x.Port - } - return 0 -} - -func (x *VIPInfo) GetBackends() []string { - if x != nil { - return x.Backends - } - return nil -} - -func (x *VIPInfo) GetDescription() string { - if x != nil { - return x.Description - } - return "" -} - -type ListBackendsResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - Backends []*BackendInfo `protobuf:"bytes,1,rep,name=backends,proto3" json:"backends,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *ListBackendsResponse) Reset() { - *x = ListBackendsResponse{} - mi := &file_proto_healthchecker_proto_msgTypes[8] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *ListBackendsResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*ListBackendsResponse) ProtoMessage() {} - -func (x *ListBackendsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_healthchecker_proto_msgTypes[8] - 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 ListBackendsResponse.ProtoReflect.Descriptor instead. -func (*ListBackendsResponse) Descriptor() ([]byte, []int) { - return file_proto_healthchecker_proto_rawDescGZIP(), []int{8} -} - -func (x *ListBackendsResponse) GetBackends() []*BackendInfo { - if x != nil { - return x.Backends - } - return nil -} - -type BackendInfo struct { - state protoimpl.MessageState `protogen:"open.v1"` - VipName string `protobuf:"bytes,1,opt,name=vip_name,json=vipName,proto3" json:"vip_name,omitempty"` - Address string `protobuf:"bytes,2,opt,name=address,proto3" json:"address,omitempty"` - State string `protobuf:"bytes,3,opt,name=state,proto3" json:"state,omitempty"` - Transitions []*TransitionRecord `protobuf:"bytes,4,rep,name=transitions,proto3" json:"transitions,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *BackendInfo) Reset() { - *x = BackendInfo{} - mi := &file_proto_healthchecker_proto_msgTypes[9] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *BackendInfo) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*BackendInfo) ProtoMessage() {} - -func (x *BackendInfo) ProtoReflect() protoreflect.Message { - mi := &file_proto_healthchecker_proto_msgTypes[9] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use BackendInfo.ProtoReflect.Descriptor instead. -func (*BackendInfo) Descriptor() ([]byte, []int) { - return file_proto_healthchecker_proto_rawDescGZIP(), []int{9} -} - -func (x *BackendInfo) GetVipName() string { - if x != nil { - return x.VipName - } - return "" -} - -func (x *BackendInfo) GetAddress() string { - if x != nil { - return x.Address - } - return "" -} - -func (x *BackendInfo) GetState() string { - if x != nil { - return x.State - } - return "" -} - -func (x *BackendInfo) GetTransitions() []*TransitionRecord { - if x != nil { - return x.Transitions - } - return nil -} - -type TransitionRecord struct { - state protoimpl.MessageState `protogen:"open.v1"` - From string `protobuf:"bytes,1,opt,name=from,proto3" json:"from,omitempty"` - To string `protobuf:"bytes,2,opt,name=to,proto3" json:"to,omitempty"` - AtUnixNs int64 `protobuf:"varint,3,opt,name=at_unix_ns,json=atUnixNs,proto3" json:"at_unix_ns,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *TransitionRecord) Reset() { - *x = TransitionRecord{} - mi := &file_proto_healthchecker_proto_msgTypes[10] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *TransitionRecord) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*TransitionRecord) ProtoMessage() {} - -func (x *TransitionRecord) ProtoReflect() protoreflect.Message { - mi := &file_proto_healthchecker_proto_msgTypes[10] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use TransitionRecord.ProtoReflect.Descriptor instead. -func (*TransitionRecord) Descriptor() ([]byte, []int) { - return file_proto_healthchecker_proto_rawDescGZIP(), []int{10} -} - -func (x *TransitionRecord) GetFrom() string { - if x != nil { - return x.From - } - return "" -} - -func (x *TransitionRecord) GetTo() string { - if x != nil { - return x.To - } - return "" -} - -func (x *TransitionRecord) GetAtUnixNs() int64 { - if x != nil { - return x.AtUnixNs - } - return 0 -} - -type TransitionEvent struct { - state protoimpl.MessageState `protogen:"open.v1"` - VipName string `protobuf:"bytes,1,opt,name=vip_name,json=vipName,proto3" json:"vip_name,omitempty"` - BackendAddress string `protobuf:"bytes,2,opt,name=backend_address,json=backendAddress,proto3" json:"backend_address,omitempty"` - Transition *TransitionRecord `protobuf:"bytes,3,opt,name=transition,proto3" json:"transition,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *TransitionEvent) Reset() { - *x = TransitionEvent{} - mi := &file_proto_healthchecker_proto_msgTypes[11] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *TransitionEvent) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*TransitionEvent) ProtoMessage() {} - -func (x *TransitionEvent) ProtoReflect() protoreflect.Message { - mi := &file_proto_healthchecker_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 TransitionEvent.ProtoReflect.Descriptor instead. -func (*TransitionEvent) Descriptor() ([]byte, []int) { - return file_proto_healthchecker_proto_rawDescGZIP(), []int{11} -} - -func (x *TransitionEvent) GetVipName() string { - if x != nil { - return x.VipName - } - return "" -} - -func (x *TransitionEvent) GetBackendAddress() string { - if x != nil { - return x.BackendAddress - } - return "" -} - -func (x *TransitionEvent) GetTransition() *TransitionRecord { - if x != nil { - return x.Transition - } - return nil -} - -var File_proto_healthchecker_proto protoreflect.FileDescriptor - -const file_proto_healthchecker_proto_rawDesc = "" + - "\n" + - "\x19proto/healthchecker.proto\x12\rhealthchecker\"\x11\n" + - "\x0fListVIPsRequest\"*\n" + - "\rGetVIPRequest\x12\x19\n" + - "\bvip_name\x18\x01 \x01(\tR\avipName\"0\n" + - "\x13ListBackendsRequest\x12\x19\n" + - "\bvip_name\x18\x01 \x01(\tR\avipName\"W\n" + - "\x11GetBackendRequest\x12\x19\n" + - "\bvip_name\x18\x01 \x01(\tR\avipName\x12'\n" + - "\x0fbackend_address\x18\x02 \x01(\tR\x0ebackendAddress\"X\n" + - "\x12PauseResumeRequest\x12\x19\n" + - "\bvip_name\x18\x01 \x01(\tR\avipName\x12'\n" + - "\x0fbackend_address\x18\x02 \x01(\tR\x0ebackendAddress\"\x0e\n" + - "\fWatchRequest\"/\n" + - "\x10ListVIPsResponse\x12\x1b\n" + - "\tvip_names\x18\x01 \x03(\tR\bvipNames\"\xa5\x01\n" + - "\aVIPInfo\x12\x12\n" + - "\x04name\x18\x01 \x01(\tR\x04name\x12\x18\n" + - "\aaddress\x18\x02 \x01(\tR\aaddress\x12\x1a\n" + - "\bprotocol\x18\x03 \x01(\tR\bprotocol\x12\x12\n" + - "\x04port\x18\x04 \x01(\rR\x04port\x12\x1a\n" + - "\bbackends\x18\x05 \x03(\tR\bbackends\x12 \n" + - "\vdescription\x18\x06 \x01(\tR\vdescription\"N\n" + - "\x14ListBackendsResponse\x126\n" + - "\bbackends\x18\x01 \x03(\v2\x1a.healthchecker.BackendInfoR\bbackends\"\x9b\x01\n" + - "\vBackendInfo\x12\x19\n" + - "\bvip_name\x18\x01 \x01(\tR\avipName\x12\x18\n" + - "\aaddress\x18\x02 \x01(\tR\aaddress\x12\x14\n" + - "\x05state\x18\x03 \x01(\tR\x05state\x12A\n" + - "\vtransitions\x18\x04 \x03(\v2\x1f.healthchecker.TransitionRecordR\vtransitions\"T\n" + - "\x10TransitionRecord\x12\x12\n" + - "\x04from\x18\x01 \x01(\tR\x04from\x12\x0e\n" + - "\x02to\x18\x02 \x01(\tR\x02to\x12\x1c\n" + - "\n" + - "at_unix_ns\x18\x03 \x01(\x03R\batUnixNs\"\x96\x01\n" + - "\x0fTransitionEvent\x12\x19\n" + - "\bvip_name\x18\x01 \x01(\tR\avipName\x12'\n" + - "\x0fbackend_address\x18\x02 \x01(\tR\x0ebackendAddress\x12?\n" + - "\n" + - "transition\x18\x03 \x01(\v2\x1f.healthchecker.TransitionRecordR\n" + - "transition2\xb3\x04\n" + - "\rHealthChecker\x12K\n" + - "\bListVIPs\x12\x1e.healthchecker.ListVIPsRequest\x1a\x1f.healthchecker.ListVIPsResponse\x12>\n" + - "\x06GetVIP\x12\x1c.healthchecker.GetVIPRequest\x1a\x16.healthchecker.VIPInfo\x12W\n" + - "\fListBackends\x12\".healthchecker.ListBackendsRequest\x1a#.healthchecker.ListBackendsResponse\x12J\n" + - "\n" + - "GetBackend\x12 .healthchecker.GetBackendRequest\x1a\x1a.healthchecker.BackendInfo\x12M\n" + - "\fPauseBackend\x12!.healthchecker.PauseResumeRequest\x1a\x1a.healthchecker.BackendInfo\x12N\n" + - "\rResumeBackend\x12!.healthchecker.PauseResumeRequest\x1a\x1a.healthchecker.BackendInfo\x12Q\n" + - "\x10WatchTransitions\x12\x1b.healthchecker.WatchRequest\x1a\x1e.healthchecker.TransitionEvent0\x01B.Z,git.ipng.ch/ipng/vpp-maglev/internal/grpcapib\x06proto3" - -var ( - file_proto_healthchecker_proto_rawDescOnce sync.Once - file_proto_healthchecker_proto_rawDescData []byte -) - -func file_proto_healthchecker_proto_rawDescGZIP() []byte { - file_proto_healthchecker_proto_rawDescOnce.Do(func() { - file_proto_healthchecker_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_proto_healthchecker_proto_rawDesc), len(file_proto_healthchecker_proto_rawDesc))) - }) - return file_proto_healthchecker_proto_rawDescData -} - -var file_proto_healthchecker_proto_msgTypes = make([]protoimpl.MessageInfo, 12) -var file_proto_healthchecker_proto_goTypes = []any{ - (*ListVIPsRequest)(nil), // 0: healthchecker.ListVIPsRequest - (*GetVIPRequest)(nil), // 1: healthchecker.GetVIPRequest - (*ListBackendsRequest)(nil), // 2: healthchecker.ListBackendsRequest - (*GetBackendRequest)(nil), // 3: healthchecker.GetBackendRequest - (*PauseResumeRequest)(nil), // 4: healthchecker.PauseResumeRequest - (*WatchRequest)(nil), // 5: healthchecker.WatchRequest - (*ListVIPsResponse)(nil), // 6: healthchecker.ListVIPsResponse - (*VIPInfo)(nil), // 7: healthchecker.VIPInfo - (*ListBackendsResponse)(nil), // 8: healthchecker.ListBackendsResponse - (*BackendInfo)(nil), // 9: healthchecker.BackendInfo - (*TransitionRecord)(nil), // 10: healthchecker.TransitionRecord - (*TransitionEvent)(nil), // 11: healthchecker.TransitionEvent -} -var file_proto_healthchecker_proto_depIdxs = []int32{ - 9, // 0: healthchecker.ListBackendsResponse.backends:type_name -> healthchecker.BackendInfo - 10, // 1: healthchecker.BackendInfo.transitions:type_name -> healthchecker.TransitionRecord - 10, // 2: healthchecker.TransitionEvent.transition:type_name -> healthchecker.TransitionRecord - 0, // 3: healthchecker.HealthChecker.ListVIPs:input_type -> healthchecker.ListVIPsRequest - 1, // 4: healthchecker.HealthChecker.GetVIP:input_type -> healthchecker.GetVIPRequest - 2, // 5: healthchecker.HealthChecker.ListBackends:input_type -> healthchecker.ListBackendsRequest - 3, // 6: healthchecker.HealthChecker.GetBackend:input_type -> healthchecker.GetBackendRequest - 4, // 7: healthchecker.HealthChecker.PauseBackend:input_type -> healthchecker.PauseResumeRequest - 4, // 8: healthchecker.HealthChecker.ResumeBackend:input_type -> healthchecker.PauseResumeRequest - 5, // 9: healthchecker.HealthChecker.WatchTransitions:input_type -> healthchecker.WatchRequest - 6, // 10: healthchecker.HealthChecker.ListVIPs:output_type -> healthchecker.ListVIPsResponse - 7, // 11: healthchecker.HealthChecker.GetVIP:output_type -> healthchecker.VIPInfo - 8, // 12: healthchecker.HealthChecker.ListBackends:output_type -> healthchecker.ListBackendsResponse - 9, // 13: healthchecker.HealthChecker.GetBackend:output_type -> healthchecker.BackendInfo - 9, // 14: healthchecker.HealthChecker.PauseBackend:output_type -> healthchecker.BackendInfo - 9, // 15: healthchecker.HealthChecker.ResumeBackend:output_type -> healthchecker.BackendInfo - 11, // 16: healthchecker.HealthChecker.WatchTransitions:output_type -> healthchecker.TransitionEvent - 10, // [10:17] is the sub-list for method output_type - 3, // [3:10] is the sub-list for method input_type - 3, // [3:3] is the sub-list for extension type_name - 3, // [3:3] is the sub-list for extension extendee - 0, // [0:3] is the sub-list for field type_name -} - -func init() { file_proto_healthchecker_proto_init() } -func file_proto_healthchecker_proto_init() { - if File_proto_healthchecker_proto != nil { - return - } - type x struct{} - out := protoimpl.TypeBuilder{ - File: protoimpl.DescBuilder{ - GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: unsafe.Slice(unsafe.StringData(file_proto_healthchecker_proto_rawDesc), len(file_proto_healthchecker_proto_rawDesc)), - NumEnums: 0, - NumMessages: 12, - NumExtensions: 0, - NumServices: 1, - }, - GoTypes: file_proto_healthchecker_proto_goTypes, - DependencyIndexes: file_proto_healthchecker_proto_depIdxs, - MessageInfos: file_proto_healthchecker_proto_msgTypes, - }.Build() - File_proto_healthchecker_proto = out.File - file_proto_healthchecker_proto_goTypes = nil - file_proto_healthchecker_proto_depIdxs = nil -} diff --git a/internal/grpcapi/healthchecker_grpc.pb.go b/internal/grpcapi/healthchecker_grpc.pb.go deleted file mode 100644 index 15f0c98..0000000 --- a/internal/grpcapi/healthchecker_grpc.pb.go +++ /dev/null @@ -1,357 +0,0 @@ -// Code generated by protoc-gen-go-grpc. DO NOT EDIT. -// versions: -// - protoc-gen-go-grpc v1.6.1 -// - protoc v3.21.12 -// source: proto/healthchecker.proto - -package grpcapi - -import ( - context "context" - grpc "google.golang.org/grpc" - codes "google.golang.org/grpc/codes" - status "google.golang.org/grpc/status" -) - -// This is a compile-time assertion to ensure that this generated file -// is compatible with the grpc package it is being compiled against. -// Requires gRPC-Go v1.64.0 or later. -const _ = grpc.SupportPackageIsVersion9 - -const ( - HealthChecker_ListVIPs_FullMethodName = "/healthchecker.HealthChecker/ListVIPs" - HealthChecker_GetVIP_FullMethodName = "/healthchecker.HealthChecker/GetVIP" - HealthChecker_ListBackends_FullMethodName = "/healthchecker.HealthChecker/ListBackends" - HealthChecker_GetBackend_FullMethodName = "/healthchecker.HealthChecker/GetBackend" - HealthChecker_PauseBackend_FullMethodName = "/healthchecker.HealthChecker/PauseBackend" - HealthChecker_ResumeBackend_FullMethodName = "/healthchecker.HealthChecker/ResumeBackend" - HealthChecker_WatchTransitions_FullMethodName = "/healthchecker.HealthChecker/WatchTransitions" -) - -// HealthCheckerClient is the client API for HealthChecker service. -// -// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. -// -// HealthChecker exposes the state of backend health for all VIPs. -type HealthCheckerClient interface { - ListVIPs(ctx context.Context, in *ListVIPsRequest, opts ...grpc.CallOption) (*ListVIPsResponse, error) - GetVIP(ctx context.Context, in *GetVIPRequest, opts ...grpc.CallOption) (*VIPInfo, error) - ListBackends(ctx context.Context, in *ListBackendsRequest, opts ...grpc.CallOption) (*ListBackendsResponse, error) - GetBackend(ctx context.Context, in *GetBackendRequest, opts ...grpc.CallOption) (*BackendInfo, error) - PauseBackend(ctx context.Context, in *PauseResumeRequest, opts ...grpc.CallOption) (*BackendInfo, error) - ResumeBackend(ctx context.Context, in *PauseResumeRequest, opts ...grpc.CallOption) (*BackendInfo, error) - WatchTransitions(ctx context.Context, in *WatchRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[TransitionEvent], error) -} - -type healthCheckerClient struct { - cc grpc.ClientConnInterface -} - -func NewHealthCheckerClient(cc grpc.ClientConnInterface) HealthCheckerClient { - return &healthCheckerClient{cc} -} - -func (c *healthCheckerClient) ListVIPs(ctx context.Context, in *ListVIPsRequest, opts ...grpc.CallOption) (*ListVIPsResponse, error) { - cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) - out := new(ListVIPsResponse) - err := c.cc.Invoke(ctx, HealthChecker_ListVIPs_FullMethodName, in, out, cOpts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *healthCheckerClient) GetVIP(ctx context.Context, in *GetVIPRequest, opts ...grpc.CallOption) (*VIPInfo, error) { - cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) - out := new(VIPInfo) - err := c.cc.Invoke(ctx, HealthChecker_GetVIP_FullMethodName, in, out, cOpts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *healthCheckerClient) ListBackends(ctx context.Context, in *ListBackendsRequest, opts ...grpc.CallOption) (*ListBackendsResponse, error) { - cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) - out := new(ListBackendsResponse) - err := c.cc.Invoke(ctx, HealthChecker_ListBackends_FullMethodName, in, out, cOpts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *healthCheckerClient) GetBackend(ctx context.Context, in *GetBackendRequest, opts ...grpc.CallOption) (*BackendInfo, error) { - cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) - out := new(BackendInfo) - err := c.cc.Invoke(ctx, HealthChecker_GetBackend_FullMethodName, in, out, cOpts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *healthCheckerClient) PauseBackend(ctx context.Context, in *PauseResumeRequest, opts ...grpc.CallOption) (*BackendInfo, error) { - cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) - out := new(BackendInfo) - err := c.cc.Invoke(ctx, HealthChecker_PauseBackend_FullMethodName, in, out, cOpts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *healthCheckerClient) ResumeBackend(ctx context.Context, in *PauseResumeRequest, opts ...grpc.CallOption) (*BackendInfo, error) { - cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) - out := new(BackendInfo) - err := c.cc.Invoke(ctx, HealthChecker_ResumeBackend_FullMethodName, in, out, cOpts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *healthCheckerClient) WatchTransitions(ctx context.Context, in *WatchRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[TransitionEvent], error) { - cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) - stream, err := c.cc.NewStream(ctx, &HealthChecker_ServiceDesc.Streams[0], HealthChecker_WatchTransitions_FullMethodName, cOpts...) - if err != nil { - return nil, err - } - x := &grpc.GenericClientStream[WatchRequest, TransitionEvent]{ClientStream: stream} - if err := x.ClientStream.SendMsg(in); err != nil { - return nil, err - } - if err := x.ClientStream.CloseSend(); err != nil { - return nil, err - } - return x, nil -} - -// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. -type HealthChecker_WatchTransitionsClient = grpc.ServerStreamingClient[TransitionEvent] - -// HealthCheckerServer is the server API for HealthChecker service. -// All implementations must embed UnimplementedHealthCheckerServer -// for forward compatibility. -// -// HealthChecker exposes the state of backend health for all VIPs. -type HealthCheckerServer interface { - ListVIPs(context.Context, *ListVIPsRequest) (*ListVIPsResponse, error) - GetVIP(context.Context, *GetVIPRequest) (*VIPInfo, error) - ListBackends(context.Context, *ListBackendsRequest) (*ListBackendsResponse, error) - GetBackend(context.Context, *GetBackendRequest) (*BackendInfo, error) - PauseBackend(context.Context, *PauseResumeRequest) (*BackendInfo, error) - ResumeBackend(context.Context, *PauseResumeRequest) (*BackendInfo, error) - WatchTransitions(*WatchRequest, grpc.ServerStreamingServer[TransitionEvent]) error - mustEmbedUnimplementedHealthCheckerServer() -} - -// UnimplementedHealthCheckerServer must be embedded to have -// forward compatible implementations. -// -// NOTE: this should be embedded by value instead of pointer to avoid a nil -// pointer dereference when methods are called. -type UnimplementedHealthCheckerServer struct{} - -func (UnimplementedHealthCheckerServer) ListVIPs(context.Context, *ListVIPsRequest) (*ListVIPsResponse, error) { - return nil, status.Error(codes.Unimplemented, "method ListVIPs not implemented") -} -func (UnimplementedHealthCheckerServer) GetVIP(context.Context, *GetVIPRequest) (*VIPInfo, error) { - return nil, status.Error(codes.Unimplemented, "method GetVIP not implemented") -} -func (UnimplementedHealthCheckerServer) ListBackends(context.Context, *ListBackendsRequest) (*ListBackendsResponse, error) { - return nil, status.Error(codes.Unimplemented, "method ListBackends not implemented") -} -func (UnimplementedHealthCheckerServer) GetBackend(context.Context, *GetBackendRequest) (*BackendInfo, error) { - return nil, status.Error(codes.Unimplemented, "method GetBackend not implemented") -} -func (UnimplementedHealthCheckerServer) PauseBackend(context.Context, *PauseResumeRequest) (*BackendInfo, error) { - return nil, status.Error(codes.Unimplemented, "method PauseBackend not implemented") -} -func (UnimplementedHealthCheckerServer) ResumeBackend(context.Context, *PauseResumeRequest) (*BackendInfo, error) { - return nil, status.Error(codes.Unimplemented, "method ResumeBackend not implemented") -} -func (UnimplementedHealthCheckerServer) WatchTransitions(*WatchRequest, grpc.ServerStreamingServer[TransitionEvent]) error { - return status.Error(codes.Unimplemented, "method WatchTransitions not implemented") -} -func (UnimplementedHealthCheckerServer) mustEmbedUnimplementedHealthCheckerServer() {} -func (UnimplementedHealthCheckerServer) testEmbeddedByValue() {} - -// UnsafeHealthCheckerServer may be embedded to opt out of forward compatibility for this service. -// Use of this interface is not recommended, as added methods to HealthCheckerServer will -// result in compilation errors. -type UnsafeHealthCheckerServer interface { - mustEmbedUnimplementedHealthCheckerServer() -} - -func RegisterHealthCheckerServer(s grpc.ServiceRegistrar, srv HealthCheckerServer) { - // If the following call panics, it indicates UnimplementedHealthCheckerServer was - // embedded by pointer and is nil. This will cause panics if an - // unimplemented method is ever invoked, so we test this at initialization - // time to prevent it from happening at runtime later due to I/O. - if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { - t.testEmbeddedByValue() - } - s.RegisterService(&HealthChecker_ServiceDesc, srv) -} - -func _HealthChecker_ListVIPs_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(ListVIPsRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(HealthCheckerServer).ListVIPs(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: HealthChecker_ListVIPs_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(HealthCheckerServer).ListVIPs(ctx, req.(*ListVIPsRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _HealthChecker_GetVIP_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(GetVIPRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(HealthCheckerServer).GetVIP(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: HealthChecker_GetVIP_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(HealthCheckerServer).GetVIP(ctx, req.(*GetVIPRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _HealthChecker_ListBackends_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(ListBackendsRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(HealthCheckerServer).ListBackends(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: HealthChecker_ListBackends_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(HealthCheckerServer).ListBackends(ctx, req.(*ListBackendsRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _HealthChecker_GetBackend_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(GetBackendRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(HealthCheckerServer).GetBackend(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: HealthChecker_GetBackend_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(HealthCheckerServer).GetBackend(ctx, req.(*GetBackendRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _HealthChecker_PauseBackend_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(PauseResumeRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(HealthCheckerServer).PauseBackend(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: HealthChecker_PauseBackend_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(HealthCheckerServer).PauseBackend(ctx, req.(*PauseResumeRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _HealthChecker_ResumeBackend_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(PauseResumeRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(HealthCheckerServer).ResumeBackend(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: HealthChecker_ResumeBackend_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(HealthCheckerServer).ResumeBackend(ctx, req.(*PauseResumeRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _HealthChecker_WatchTransitions_Handler(srv interface{}, stream grpc.ServerStream) error { - m := new(WatchRequest) - if err := stream.RecvMsg(m); err != nil { - return err - } - return srv.(HealthCheckerServer).WatchTransitions(m, &grpc.GenericServerStream[WatchRequest, TransitionEvent]{ServerStream: stream}) -} - -// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. -type HealthChecker_WatchTransitionsServer = grpc.ServerStreamingServer[TransitionEvent] - -// HealthChecker_ServiceDesc is the grpc.ServiceDesc for HealthChecker service. -// It's only intended for direct use with grpc.RegisterService, -// and not to be introspected or modified (even as a copy) -var HealthChecker_ServiceDesc = grpc.ServiceDesc{ - ServiceName: "healthchecker.HealthChecker", - HandlerType: (*HealthCheckerServer)(nil), - Methods: []grpc.MethodDesc{ - { - MethodName: "ListVIPs", - Handler: _HealthChecker_ListVIPs_Handler, - }, - { - MethodName: "GetVIP", - Handler: _HealthChecker_GetVIP_Handler, - }, - { - MethodName: "ListBackends", - Handler: _HealthChecker_ListBackends_Handler, - }, - { - MethodName: "GetBackend", - Handler: _HealthChecker_GetBackend_Handler, - }, - { - MethodName: "PauseBackend", - Handler: _HealthChecker_PauseBackend_Handler, - }, - { - MethodName: "ResumeBackend", - Handler: _HealthChecker_ResumeBackend_Handler, - }, - }, - Streams: []grpc.StreamDesc{ - { - StreamName: "WatchTransitions", - Handler: _HealthChecker_WatchTransitions_Handler, - ServerStreams: true, - }, - }, - Metadata: "proto/healthchecker.proto", -} diff --git a/internal/grpcapi/maglev.pb.go b/internal/grpcapi/maglev.pb.go new file mode 100644 index 0000000..6d9c370 --- /dev/null +++ b/internal/grpcapi/maglev.pb.go @@ -0,0 +1,1232 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.11 +// protoc v3.21.12 +// source: proto/maglev.proto + +package grpcapi + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type ListFrontendsRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListFrontendsRequest) Reset() { + *x = ListFrontendsRequest{} + mi := &file_proto_maglev_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListFrontendsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListFrontendsRequest) ProtoMessage() {} + +func (x *ListFrontendsRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_maglev_proto_msgTypes[0] + 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 ListFrontendsRequest.ProtoReflect.Descriptor instead. +func (*ListFrontendsRequest) Descriptor() ([]byte, []int) { + return file_proto_maglev_proto_rawDescGZIP(), []int{0} +} + +type GetFrontendRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetFrontendRequest) Reset() { + *x = GetFrontendRequest{} + mi := &file_proto_maglev_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetFrontendRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetFrontendRequest) ProtoMessage() {} + +func (x *GetFrontendRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_maglev_proto_msgTypes[1] + 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 GetFrontendRequest.ProtoReflect.Descriptor instead. +func (*GetFrontendRequest) Descriptor() ([]byte, []int) { + return file_proto_maglev_proto_rawDescGZIP(), []int{1} +} + +func (x *GetFrontendRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +type ListBackendsRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListBackendsRequest) Reset() { + *x = ListBackendsRequest{} + mi := &file_proto_maglev_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListBackendsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListBackendsRequest) ProtoMessage() {} + +func (x *ListBackendsRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_maglev_proto_msgTypes[2] + 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 ListBackendsRequest.ProtoReflect.Descriptor instead. +func (*ListBackendsRequest) Descriptor() ([]byte, []int) { + return file_proto_maglev_proto_rawDescGZIP(), []int{2} +} + +type GetBackendRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetBackendRequest) Reset() { + *x = GetBackendRequest{} + mi := &file_proto_maglev_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetBackendRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetBackendRequest) ProtoMessage() {} + +func (x *GetBackendRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_maglev_proto_msgTypes[3] + 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 GetBackendRequest.ProtoReflect.Descriptor instead. +func (*GetBackendRequest) Descriptor() ([]byte, []int) { + return file_proto_maglev_proto_rawDescGZIP(), []int{3} +} + +func (x *GetBackendRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +type PauseResumeRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PauseResumeRequest) Reset() { + *x = PauseResumeRequest{} + mi := &file_proto_maglev_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PauseResumeRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PauseResumeRequest) ProtoMessage() {} + +func (x *PauseResumeRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_maglev_proto_msgTypes[4] + 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 PauseResumeRequest.ProtoReflect.Descriptor instead. +func (*PauseResumeRequest) Descriptor() ([]byte, []int) { + return file_proto_maglev_proto_rawDescGZIP(), []int{4} +} + +func (x *PauseResumeRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +type ListHealthChecksRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListHealthChecksRequest) Reset() { + *x = ListHealthChecksRequest{} + mi := &file_proto_maglev_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListHealthChecksRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListHealthChecksRequest) ProtoMessage() {} + +func (x *ListHealthChecksRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_maglev_proto_msgTypes[5] + 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 ListHealthChecksRequest.ProtoReflect.Descriptor instead. +func (*ListHealthChecksRequest) Descriptor() ([]byte, []int) { + return file_proto_maglev_proto_rawDescGZIP(), []int{5} +} + +type GetHealthCheckRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetHealthCheckRequest) Reset() { + *x = GetHealthCheckRequest{} + mi := &file_proto_maglev_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetHealthCheckRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetHealthCheckRequest) ProtoMessage() {} + +func (x *GetHealthCheckRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_maglev_proto_msgTypes[6] + 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 GetHealthCheckRequest.ProtoReflect.Descriptor instead. +func (*GetHealthCheckRequest) Descriptor() ([]byte, []int) { + return file_proto_maglev_proto_rawDescGZIP(), []int{6} +} + +func (x *GetHealthCheckRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +type WatchRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *WatchRequest) Reset() { + *x = WatchRequest{} + mi := &file_proto_maglev_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *WatchRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*WatchRequest) ProtoMessage() {} + +func (x *WatchRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_maglev_proto_msgTypes[7] + 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 WatchRequest.ProtoReflect.Descriptor instead. +func (*WatchRequest) Descriptor() ([]byte, []int) { + return file_proto_maglev_proto_rawDescGZIP(), []int{7} +} + +type ListFrontendsResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + FrontendNames []string `protobuf:"bytes,1,rep,name=frontend_names,json=frontendNames,proto3" json:"frontend_names,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListFrontendsResponse) Reset() { + *x = ListFrontendsResponse{} + mi := &file_proto_maglev_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListFrontendsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListFrontendsResponse) ProtoMessage() {} + +func (x *ListFrontendsResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_maglev_proto_msgTypes[8] + 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 ListFrontendsResponse.ProtoReflect.Descriptor instead. +func (*ListFrontendsResponse) Descriptor() ([]byte, []int) { + return file_proto_maglev_proto_rawDescGZIP(), []int{8} +} + +func (x *ListFrontendsResponse) GetFrontendNames() []string { + if x != nil { + return x.FrontendNames + } + return nil +} + +type FrontendInfo struct { + state protoimpl.MessageState `protogen:"open.v1"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Address string `protobuf:"bytes,2,opt,name=address,proto3" json:"address,omitempty"` + Protocol string `protobuf:"bytes,3,opt,name=protocol,proto3" json:"protocol,omitempty"` + Port uint32 `protobuf:"varint,4,opt,name=port,proto3" json:"port,omitempty"` + BackendNames []string `protobuf:"bytes,5,rep,name=backend_names,json=backendNames,proto3" json:"backend_names,omitempty"` + Description string `protobuf:"bytes,6,opt,name=description,proto3" json:"description,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *FrontendInfo) Reset() { + *x = FrontendInfo{} + mi := &file_proto_maglev_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *FrontendInfo) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*FrontendInfo) ProtoMessage() {} + +func (x *FrontendInfo) ProtoReflect() protoreflect.Message { + mi := &file_proto_maglev_proto_msgTypes[9] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use FrontendInfo.ProtoReflect.Descriptor instead. +func (*FrontendInfo) Descriptor() ([]byte, []int) { + return file_proto_maglev_proto_rawDescGZIP(), []int{9} +} + +func (x *FrontendInfo) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *FrontendInfo) GetAddress() string { + if x != nil { + return x.Address + } + return "" +} + +func (x *FrontendInfo) GetProtocol() string { + if x != nil { + return x.Protocol + } + return "" +} + +func (x *FrontendInfo) GetPort() uint32 { + if x != nil { + return x.Port + } + return 0 +} + +func (x *FrontendInfo) GetBackendNames() []string { + if x != nil { + return x.BackendNames + } + return nil +} + +func (x *FrontendInfo) GetDescription() string { + if x != nil { + return x.Description + } + return "" +} + +type ListBackendsResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + BackendNames []string `protobuf:"bytes,1,rep,name=backend_names,json=backendNames,proto3" json:"backend_names,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListBackendsResponse) Reset() { + *x = ListBackendsResponse{} + mi := &file_proto_maglev_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListBackendsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListBackendsResponse) ProtoMessage() {} + +func (x *ListBackendsResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_maglev_proto_msgTypes[10] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListBackendsResponse.ProtoReflect.Descriptor instead. +func (*ListBackendsResponse) Descriptor() ([]byte, []int) { + return file_proto_maglev_proto_rawDescGZIP(), []int{10} +} + +func (x *ListBackendsResponse) GetBackendNames() []string { + if x != nil { + return x.BackendNames + } + return nil +} + +type ListHealthChecksResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Names []string `protobuf:"bytes,1,rep,name=names,proto3" json:"names,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListHealthChecksResponse) Reset() { + *x = ListHealthChecksResponse{} + mi := &file_proto_maglev_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListHealthChecksResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListHealthChecksResponse) ProtoMessage() {} + +func (x *ListHealthChecksResponse) 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 ListHealthChecksResponse.ProtoReflect.Descriptor instead. +func (*ListHealthChecksResponse) Descriptor() ([]byte, []int) { + return file_proto_maglev_proto_rawDescGZIP(), []int{11} +} + +func (x *ListHealthChecksResponse) GetNames() []string { + if x != nil { + return x.Names + } + return nil +} + +type HTTPCheckParams struct { + state protoimpl.MessageState `protogen:"open.v1"` + Path string `protobuf:"bytes,1,opt,name=path,proto3" json:"path,omitempty"` + Host string `protobuf:"bytes,2,opt,name=host,proto3" json:"host,omitempty"` + ResponseCodeMin int32 `protobuf:"varint,3,opt,name=response_code_min,json=responseCodeMin,proto3" json:"response_code_min,omitempty"` + ResponseCodeMax int32 `protobuf:"varint,4,opt,name=response_code_max,json=responseCodeMax,proto3" json:"response_code_max,omitempty"` + ResponseRegexp string `protobuf:"bytes,5,opt,name=response_regexp,json=responseRegexp,proto3" json:"response_regexp,omitempty"` + ServerName string `protobuf:"bytes,6,opt,name=server_name,json=serverName,proto3" json:"server_name,omitempty"` + InsecureSkipVerify bool `protobuf:"varint,7,opt,name=insecure_skip_verify,json=insecureSkipVerify,proto3" json:"insecure_skip_verify,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *HTTPCheckParams) Reset() { + *x = HTTPCheckParams{} + mi := &file_proto_maglev_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *HTTPCheckParams) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HTTPCheckParams) ProtoMessage() {} + +func (x *HTTPCheckParams) 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 HTTPCheckParams.ProtoReflect.Descriptor instead. +func (*HTTPCheckParams) Descriptor() ([]byte, []int) { + return file_proto_maglev_proto_rawDescGZIP(), []int{12} +} + +func (x *HTTPCheckParams) GetPath() string { + if x != nil { + return x.Path + } + return "" +} + +func (x *HTTPCheckParams) GetHost() string { + if x != nil { + return x.Host + } + return "" +} + +func (x *HTTPCheckParams) GetResponseCodeMin() int32 { + if x != nil { + return x.ResponseCodeMin + } + return 0 +} + +func (x *HTTPCheckParams) GetResponseCodeMax() int32 { + if x != nil { + return x.ResponseCodeMax + } + return 0 +} + +func (x *HTTPCheckParams) GetResponseRegexp() string { + if x != nil { + return x.ResponseRegexp + } + return "" +} + +func (x *HTTPCheckParams) GetServerName() string { + if x != nil { + return x.ServerName + } + return "" +} + +func (x *HTTPCheckParams) GetInsecureSkipVerify() bool { + if x != nil { + return x.InsecureSkipVerify + } + return false +} + +type TCPCheckParams struct { + state protoimpl.MessageState `protogen:"open.v1"` + Ssl bool `protobuf:"varint,1,opt,name=ssl,proto3" json:"ssl,omitempty"` + ServerName string `protobuf:"bytes,2,opt,name=server_name,json=serverName,proto3" json:"server_name,omitempty"` + InsecureSkipVerify bool `protobuf:"varint,3,opt,name=insecure_skip_verify,json=insecureSkipVerify,proto3" json:"insecure_skip_verify,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TCPCheckParams) Reset() { + *x = TCPCheckParams{} + mi := &file_proto_maglev_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TCPCheckParams) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TCPCheckParams) ProtoMessage() {} + +func (x *TCPCheckParams) ProtoReflect() protoreflect.Message { + mi := &file_proto_maglev_proto_msgTypes[13] + 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 TCPCheckParams.ProtoReflect.Descriptor instead. +func (*TCPCheckParams) Descriptor() ([]byte, []int) { + return file_proto_maglev_proto_rawDescGZIP(), []int{13} +} + +func (x *TCPCheckParams) GetSsl() bool { + if x != nil { + return x.Ssl + } + return false +} + +func (x *TCPCheckParams) GetServerName() string { + if x != nil { + return x.ServerName + } + return "" +} + +func (x *TCPCheckParams) GetInsecureSkipVerify() bool { + if x != nil { + return x.InsecureSkipVerify + } + return false +} + +type HealthCheckInfo struct { + state protoimpl.MessageState `protogen:"open.v1"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Type string `protobuf:"bytes,2,opt,name=type,proto3" json:"type,omitempty"` + Port uint32 `protobuf:"varint,3,opt,name=port,proto3" json:"port,omitempty"` + ProbeIpv4Src string `protobuf:"bytes,4,opt,name=probe_ipv4_src,json=probeIpv4Src,proto3" json:"probe_ipv4_src,omitempty"` + ProbeIpv6Src string `protobuf:"bytes,5,opt,name=probe_ipv6_src,json=probeIpv6Src,proto3" json:"probe_ipv6_src,omitempty"` + IntervalNs int64 `protobuf:"varint,6,opt,name=interval_ns,json=intervalNs,proto3" json:"interval_ns,omitempty"` + FastIntervalNs int64 `protobuf:"varint,7,opt,name=fast_interval_ns,json=fastIntervalNs,proto3" json:"fast_interval_ns,omitempty"` + DownIntervalNs int64 `protobuf:"varint,8,opt,name=down_interval_ns,json=downIntervalNs,proto3" json:"down_interval_ns,omitempty"` + TimeoutNs int64 `protobuf:"varint,9,opt,name=timeout_ns,json=timeoutNs,proto3" json:"timeout_ns,omitempty"` + Rise int32 `protobuf:"varint,10,opt,name=rise,proto3" json:"rise,omitempty"` + Fall int32 `protobuf:"varint,11,opt,name=fall,proto3" json:"fall,omitempty"` + Http *HTTPCheckParams `protobuf:"bytes,12,opt,name=http,proto3" json:"http,omitempty"` + Tcp *TCPCheckParams `protobuf:"bytes,13,opt,name=tcp,proto3" json:"tcp,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *HealthCheckInfo) Reset() { + *x = HealthCheckInfo{} + mi := &file_proto_maglev_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *HealthCheckInfo) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HealthCheckInfo) ProtoMessage() {} + +func (x *HealthCheckInfo) ProtoReflect() protoreflect.Message { + mi := &file_proto_maglev_proto_msgTypes[14] + 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 HealthCheckInfo.ProtoReflect.Descriptor instead. +func (*HealthCheckInfo) Descriptor() ([]byte, []int) { + return file_proto_maglev_proto_rawDescGZIP(), []int{14} +} + +func (x *HealthCheckInfo) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *HealthCheckInfo) GetType() string { + if x != nil { + return x.Type + } + return "" +} + +func (x *HealthCheckInfo) GetPort() uint32 { + if x != nil { + return x.Port + } + return 0 +} + +func (x *HealthCheckInfo) GetProbeIpv4Src() string { + if x != nil { + return x.ProbeIpv4Src + } + return "" +} + +func (x *HealthCheckInfo) GetProbeIpv6Src() string { + if x != nil { + return x.ProbeIpv6Src + } + return "" +} + +func (x *HealthCheckInfo) GetIntervalNs() int64 { + if x != nil { + return x.IntervalNs + } + return 0 +} + +func (x *HealthCheckInfo) GetFastIntervalNs() int64 { + if x != nil { + return x.FastIntervalNs + } + return 0 +} + +func (x *HealthCheckInfo) GetDownIntervalNs() int64 { + if x != nil { + return x.DownIntervalNs + } + return 0 +} + +func (x *HealthCheckInfo) GetTimeoutNs() int64 { + if x != nil { + return x.TimeoutNs + } + return 0 +} + +func (x *HealthCheckInfo) GetRise() int32 { + if x != nil { + return x.Rise + } + return 0 +} + +func (x *HealthCheckInfo) GetFall() int32 { + if x != nil { + return x.Fall + } + return 0 +} + +func (x *HealthCheckInfo) GetHttp() *HTTPCheckParams { + if x != nil { + return x.Http + } + return nil +} + +func (x *HealthCheckInfo) GetTcp() *TCPCheckParams { + if x != nil { + return x.Tcp + } + return nil +} + +type BackendInfo struct { + state protoimpl.MessageState `protogen:"open.v1"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Address string `protobuf:"bytes,2,opt,name=address,proto3" json:"address,omitempty"` + State string `protobuf:"bytes,3,opt,name=state,proto3" json:"state,omitempty"` + Transitions []*TransitionRecord `protobuf:"bytes,4,rep,name=transitions,proto3" json:"transitions,omitempty"` + Enabled bool `protobuf:"varint,5,opt,name=enabled,proto3" json:"enabled,omitempty"` + Weight int32 `protobuf:"varint,6,opt,name=weight,proto3" json:"weight,omitempty"` + Healthcheck string `protobuf:"bytes,7,opt,name=healthcheck,proto3" json:"healthcheck,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *BackendInfo) Reset() { + *x = BackendInfo{} + mi := &file_proto_maglev_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *BackendInfo) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BackendInfo) ProtoMessage() {} + +func (x *BackendInfo) ProtoReflect() protoreflect.Message { + mi := &file_proto_maglev_proto_msgTypes[15] + 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 BackendInfo.ProtoReflect.Descriptor instead. +func (*BackendInfo) Descriptor() ([]byte, []int) { + return file_proto_maglev_proto_rawDescGZIP(), []int{15} +} + +func (x *BackendInfo) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *BackendInfo) GetAddress() string { + if x != nil { + return x.Address + } + return "" +} + +func (x *BackendInfo) GetState() string { + if x != nil { + return x.State + } + return "" +} + +func (x *BackendInfo) GetTransitions() []*TransitionRecord { + if x != nil { + return x.Transitions + } + return nil +} + +func (x *BackendInfo) GetEnabled() bool { + if x != nil { + return x.Enabled + } + return false +} + +func (x *BackendInfo) GetWeight() int32 { + if x != nil { + return x.Weight + } + return 0 +} + +func (x *BackendInfo) GetHealthcheck() string { + if x != nil { + return x.Healthcheck + } + return "" +} + +type TransitionRecord struct { + state protoimpl.MessageState `protogen:"open.v1"` + From string `protobuf:"bytes,1,opt,name=from,proto3" json:"from,omitempty"` + To string `protobuf:"bytes,2,opt,name=to,proto3" json:"to,omitempty"` + AtUnixNs int64 `protobuf:"varint,3,opt,name=at_unix_ns,json=atUnixNs,proto3" json:"at_unix_ns,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TransitionRecord) Reset() { + *x = TransitionRecord{} + mi := &file_proto_maglev_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TransitionRecord) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TransitionRecord) ProtoMessage() {} + +func (x *TransitionRecord) ProtoReflect() protoreflect.Message { + mi := &file_proto_maglev_proto_msgTypes[16] + 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 TransitionRecord.ProtoReflect.Descriptor instead. +func (*TransitionRecord) Descriptor() ([]byte, []int) { + return file_proto_maglev_proto_rawDescGZIP(), []int{16} +} + +func (x *TransitionRecord) GetFrom() string { + if x != nil { + return x.From + } + return "" +} + +func (x *TransitionRecord) GetTo() string { + if x != nil { + return x.To + } + return "" +} + +func (x *TransitionRecord) GetAtUnixNs() int64 { + if x != nil { + return x.AtUnixNs + } + return 0 +} + +type BackendEvent struct { + state protoimpl.MessageState `protogen:"open.v1"` + BackendName string `protobuf:"bytes,1,opt,name=backend_name,json=backendName,proto3" json:"backend_name,omitempty"` + Transition *TransitionRecord `protobuf:"bytes,2,opt,name=transition,proto3" json:"transition,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *BackendEvent) Reset() { + *x = BackendEvent{} + mi := &file_proto_maglev_proto_msgTypes[17] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *BackendEvent) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BackendEvent) ProtoMessage() {} + +func (x *BackendEvent) ProtoReflect() protoreflect.Message { + mi := &file_proto_maglev_proto_msgTypes[17] + 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 BackendEvent.ProtoReflect.Descriptor instead. +func (*BackendEvent) Descriptor() ([]byte, []int) { + return file_proto_maglev_proto_rawDescGZIP(), []int{17} +} + +func (x *BackendEvent) GetBackendName() string { + if x != nil { + return x.BackendName + } + return "" +} + +func (x *BackendEvent) GetTransition() *TransitionRecord { + if x != nil { + return x.Transition + } + return nil +} + +var File_proto_maglev_proto protoreflect.FileDescriptor + +const file_proto_maglev_proto_rawDesc = "" + + "\n" + + "\x12proto/maglev.proto\x12\x06maglev\"\x16\n" + + "\x14ListFrontendsRequest\"(\n" + + "\x12GetFrontendRequest\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\"\x15\n" + + "\x13ListBackendsRequest\"'\n" + + "\x11GetBackendRequest\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\"(\n" + + "\x12PauseResumeRequest\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\"\x19\n" + + "\x17ListHealthChecksRequest\"+\n" + + "\x15GetHealthCheckRequest\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\"\x0e\n" + + "\fWatchRequest\">\n" + + "\x15ListFrontendsResponse\x12%\n" + + "\x0efrontend_names\x18\x01 \x03(\tR\rfrontendNames\"\xb3\x01\n" + + "\fFrontendInfo\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12\x18\n" + + "\aaddress\x18\x02 \x01(\tR\aaddress\x12\x1a\n" + + "\bprotocol\x18\x03 \x01(\tR\bprotocol\x12\x12\n" + + "\x04port\x18\x04 \x01(\rR\x04port\x12#\n" + + "\rbackend_names\x18\x05 \x03(\tR\fbackendNames\x12 \n" + + "\vdescription\x18\x06 \x01(\tR\vdescription\";\n" + + "\x14ListBackendsResponse\x12#\n" + + "\rbackend_names\x18\x01 \x03(\tR\fbackendNames\"0\n" + + "\x18ListHealthChecksResponse\x12\x14\n" + + "\x05names\x18\x01 \x03(\tR\x05names\"\x8d\x02\n" + + "\x0fHTTPCheckParams\x12\x12\n" + + "\x04path\x18\x01 \x01(\tR\x04path\x12\x12\n" + + "\x04host\x18\x02 \x01(\tR\x04host\x12*\n" + + "\x11response_code_min\x18\x03 \x01(\x05R\x0fresponseCodeMin\x12*\n" + + "\x11response_code_max\x18\x04 \x01(\x05R\x0fresponseCodeMax\x12'\n" + + "\x0fresponse_regexp\x18\x05 \x01(\tR\x0eresponseRegexp\x12\x1f\n" + + "\vserver_name\x18\x06 \x01(\tR\n" + + "serverName\x120\n" + + "\x14insecure_skip_verify\x18\a \x01(\bR\x12insecureSkipVerify\"u\n" + + "\x0eTCPCheckParams\x12\x10\n" + + "\x03ssl\x18\x01 \x01(\bR\x03ssl\x12\x1f\n" + + "\vserver_name\x18\x02 \x01(\tR\n" + + "serverName\x120\n" + + "\x14insecure_skip_verify\x18\x03 \x01(\bR\x12insecureSkipVerify\"\xac\x03\n" + + "\x0fHealthCheckInfo\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12\x12\n" + + "\x04type\x18\x02 \x01(\tR\x04type\x12\x12\n" + + "\x04port\x18\x03 \x01(\rR\x04port\x12$\n" + + "\x0eprobe_ipv4_src\x18\x04 \x01(\tR\fprobeIpv4Src\x12$\n" + + "\x0eprobe_ipv6_src\x18\x05 \x01(\tR\fprobeIpv6Src\x12\x1f\n" + + "\vinterval_ns\x18\x06 \x01(\x03R\n" + + "intervalNs\x12(\n" + + "\x10fast_interval_ns\x18\a \x01(\x03R\x0efastIntervalNs\x12(\n" + + "\x10down_interval_ns\x18\b \x01(\x03R\x0edownIntervalNs\x12\x1d\n" + + "\n" + + "timeout_ns\x18\t \x01(\x03R\ttimeoutNs\x12\x12\n" + + "\x04rise\x18\n" + + " \x01(\x05R\x04rise\x12\x12\n" + + "\x04fall\x18\v \x01(\x05R\x04fall\x12+\n" + + "\x04http\x18\f \x01(\v2\x17.maglev.HTTPCheckParamsR\x04http\x12(\n" + + "\x03tcp\x18\r \x01(\v2\x16.maglev.TCPCheckParamsR\x03tcp\"\xe1\x01\n" + + "\vBackendInfo\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12\x18\n" + + "\aaddress\x18\x02 \x01(\tR\aaddress\x12\x14\n" + + "\x05state\x18\x03 \x01(\tR\x05state\x12:\n" + + "\vtransitions\x18\x04 \x03(\v2\x18.maglev.TransitionRecordR\vtransitions\x12\x18\n" + + "\aenabled\x18\x05 \x01(\bR\aenabled\x12\x16\n" + + "\x06weight\x18\x06 \x01(\x05R\x06weight\x12 \n" + + "\vhealthcheck\x18\a \x01(\tR\vhealthcheck\"T\n" + + "\x10TransitionRecord\x12\x12\n" + + "\x04from\x18\x01 \x01(\tR\x04from\x12\x0e\n" + + "\x02to\x18\x02 \x01(\tR\x02to\x12\x1c\n" + + "\n" + + "at_unix_ns\x18\x03 \x01(\x03R\batUnixNs\"k\n" + + "\fBackendEvent\x12!\n" + + "\fbackend_name\x18\x01 \x01(\tR\vbackendName\x128\n" + + "\n" + + "transition\x18\x02 \x01(\v2\x18.maglev.TransitionRecordR\n" + + "transition2\x88\x05\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" + + "\fListBackends\x12\x1b.maglev.ListBackendsRequest\x1a\x1c.maglev.ListBackendsResponse\x12<\n" + + "\n" + + "GetBackend\x12\x19.maglev.GetBackendRequest\x1a\x13.maglev.BackendInfo\x12?\n" + + "\fPauseBackend\x12\x1a.maglev.PauseResumeRequest\x1a\x13.maglev.BackendInfo\x12@\n" + + "\rResumeBackend\x12\x1a.maglev.PauseResumeRequest\x1a\x13.maglev.BackendInfo\x12U\n" + + "\x10ListHealthChecks\x12\x1f.maglev.ListHealthChecksRequest\x1a .maglev.ListHealthChecksResponse\x12H\n" + + "\x0eGetHealthCheck\x12\x1d.maglev.GetHealthCheckRequest\x1a\x17.maglev.HealthCheckInfo\x12B\n" + + "\x12WatchBackendEvents\x12\x14.maglev.WatchRequest\x1a\x14.maglev.BackendEvent0\x01B.Z,git.ipng.ch/ipng/vpp-maglev/internal/grpcapib\x06proto3" + +var ( + file_proto_maglev_proto_rawDescOnce sync.Once + file_proto_maglev_proto_rawDescData []byte +) + +func file_proto_maglev_proto_rawDescGZIP() []byte { + file_proto_maglev_proto_rawDescOnce.Do(func() { + file_proto_maglev_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_proto_maglev_proto_rawDesc), len(file_proto_maglev_proto_rawDesc))) + }) + return file_proto_maglev_proto_rawDescData +} + +var file_proto_maglev_proto_msgTypes = make([]protoimpl.MessageInfo, 18) +var file_proto_maglev_proto_goTypes = []any{ + (*ListFrontendsRequest)(nil), // 0: maglev.ListFrontendsRequest + (*GetFrontendRequest)(nil), // 1: maglev.GetFrontendRequest + (*ListBackendsRequest)(nil), // 2: maglev.ListBackendsRequest + (*GetBackendRequest)(nil), // 3: maglev.GetBackendRequest + (*PauseResumeRequest)(nil), // 4: maglev.PauseResumeRequest + (*ListHealthChecksRequest)(nil), // 5: maglev.ListHealthChecksRequest + (*GetHealthCheckRequest)(nil), // 6: maglev.GetHealthCheckRequest + (*WatchRequest)(nil), // 7: maglev.WatchRequest + (*ListFrontendsResponse)(nil), // 8: maglev.ListFrontendsResponse + (*FrontendInfo)(nil), // 9: maglev.FrontendInfo + (*ListBackendsResponse)(nil), // 10: maglev.ListBackendsResponse + (*ListHealthChecksResponse)(nil), // 11: maglev.ListHealthChecksResponse + (*HTTPCheckParams)(nil), // 12: maglev.HTTPCheckParams + (*TCPCheckParams)(nil), // 13: maglev.TCPCheckParams + (*HealthCheckInfo)(nil), // 14: maglev.HealthCheckInfo + (*BackendInfo)(nil), // 15: maglev.BackendInfo + (*TransitionRecord)(nil), // 16: maglev.TransitionRecord + (*BackendEvent)(nil), // 17: maglev.BackendEvent +} +var file_proto_maglev_proto_depIdxs = []int32{ + 12, // 0: maglev.HealthCheckInfo.http:type_name -> maglev.HTTPCheckParams + 13, // 1: maglev.HealthCheckInfo.tcp:type_name -> maglev.TCPCheckParams + 16, // 2: maglev.BackendInfo.transitions:type_name -> maglev.TransitionRecord + 16, // 3: maglev.BackendEvent.transition:type_name -> maglev.TransitionRecord + 0, // 4: maglev.Maglev.ListFrontends:input_type -> maglev.ListFrontendsRequest + 1, // 5: maglev.Maglev.GetFrontend:input_type -> maglev.GetFrontendRequest + 2, // 6: maglev.Maglev.ListBackends:input_type -> maglev.ListBackendsRequest + 3, // 7: maglev.Maglev.GetBackend:input_type -> maglev.GetBackendRequest + 4, // 8: maglev.Maglev.PauseBackend:input_type -> maglev.PauseResumeRequest + 4, // 9: maglev.Maglev.ResumeBackend:input_type -> maglev.PauseResumeRequest + 5, // 10: maglev.Maglev.ListHealthChecks:input_type -> maglev.ListHealthChecksRequest + 6, // 11: maglev.Maglev.GetHealthCheck:input_type -> maglev.GetHealthCheckRequest + 7, // 12: maglev.Maglev.WatchBackendEvents:input_type -> maglev.WatchRequest + 8, // 13: maglev.Maglev.ListFrontends:output_type -> maglev.ListFrontendsResponse + 9, // 14: maglev.Maglev.GetFrontend:output_type -> maglev.FrontendInfo + 10, // 15: maglev.Maglev.ListBackends:output_type -> maglev.ListBackendsResponse + 15, // 16: maglev.Maglev.GetBackend:output_type -> maglev.BackendInfo + 15, // 17: maglev.Maglev.PauseBackend:output_type -> maglev.BackendInfo + 15, // 18: maglev.Maglev.ResumeBackend:output_type -> maglev.BackendInfo + 11, // 19: maglev.Maglev.ListHealthChecks:output_type -> maglev.ListHealthChecksResponse + 14, // 20: maglev.Maglev.GetHealthCheck:output_type -> maglev.HealthCheckInfo + 17, // 21: maglev.Maglev.WatchBackendEvents:output_type -> maglev.BackendEvent + 13, // [13:22] is the sub-list for method output_type + 4, // [4:13] is the sub-list for method input_type + 4, // [4:4] is the sub-list for extension type_name + 4, // [4:4] is the sub-list for extension extendee + 0, // [0:4] is the sub-list for field type_name +} + +func init() { file_proto_maglev_proto_init() } +func file_proto_maglev_proto_init() { + if File_proto_maglev_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_proto_maglev_proto_rawDesc), len(file_proto_maglev_proto_rawDesc)), + NumEnums: 0, + NumMessages: 18, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_proto_maglev_proto_goTypes, + DependencyIndexes: file_proto_maglev_proto_depIdxs, + MessageInfos: file_proto_maglev_proto_msgTypes, + }.Build() + File_proto_maglev_proto = out.File + file_proto_maglev_proto_goTypes = nil + file_proto_maglev_proto_depIdxs = nil +} diff --git a/internal/grpcapi/maglev_grpc.pb.go b/internal/grpcapi/maglev_grpc.pb.go new file mode 100644 index 0000000..1794ab0 --- /dev/null +++ b/internal/grpcapi/maglev_grpc.pb.go @@ -0,0 +1,433 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.6.1 +// - protoc v3.21.12 +// source: proto/maglev.proto + +package grpcapi + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.64.0 or later. +const _ = grpc.SupportPackageIsVersion9 + +const ( + Maglev_ListFrontends_FullMethodName = "/maglev.Maglev/ListFrontends" + Maglev_GetFrontend_FullMethodName = "/maglev.Maglev/GetFrontend" + Maglev_ListBackends_FullMethodName = "/maglev.Maglev/ListBackends" + Maglev_GetBackend_FullMethodName = "/maglev.Maglev/GetBackend" + Maglev_PauseBackend_FullMethodName = "/maglev.Maglev/PauseBackend" + Maglev_ResumeBackend_FullMethodName = "/maglev.Maglev/ResumeBackend" + Maglev_ListHealthChecks_FullMethodName = "/maglev.Maglev/ListHealthChecks" + Maglev_GetHealthCheck_FullMethodName = "/maglev.Maglev/GetHealthCheck" + Maglev_WatchBackendEvents_FullMethodName = "/maglev.Maglev/WatchBackendEvents" +) + +// MaglevClient is the client API for Maglev service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +// +// Maglev exposes the state of backend health for all frontends. +type MaglevClient interface { + ListFrontends(ctx context.Context, in *ListFrontendsRequest, opts ...grpc.CallOption) (*ListFrontendsResponse, error) + GetFrontend(ctx context.Context, in *GetFrontendRequest, opts ...grpc.CallOption) (*FrontendInfo, error) + ListBackends(ctx context.Context, in *ListBackendsRequest, opts ...grpc.CallOption) (*ListBackendsResponse, error) + GetBackend(ctx context.Context, in *GetBackendRequest, opts ...grpc.CallOption) (*BackendInfo, error) + PauseBackend(ctx context.Context, in *PauseResumeRequest, opts ...grpc.CallOption) (*BackendInfo, error) + ResumeBackend(ctx context.Context, in *PauseResumeRequest, opts ...grpc.CallOption) (*BackendInfo, error) + ListHealthChecks(ctx context.Context, in *ListHealthChecksRequest, opts ...grpc.CallOption) (*ListHealthChecksResponse, error) + GetHealthCheck(ctx context.Context, in *GetHealthCheckRequest, opts ...grpc.CallOption) (*HealthCheckInfo, error) + WatchBackendEvents(ctx context.Context, in *WatchRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[BackendEvent], error) +} + +type maglevClient struct { + cc grpc.ClientConnInterface +} + +func NewMaglevClient(cc grpc.ClientConnInterface) MaglevClient { + return &maglevClient{cc} +} + +func (c *maglevClient) ListFrontends(ctx context.Context, in *ListFrontendsRequest, opts ...grpc.CallOption) (*ListFrontendsResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ListFrontendsResponse) + err := c.cc.Invoke(ctx, Maglev_ListFrontends_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *maglevClient) GetFrontend(ctx context.Context, in *GetFrontendRequest, opts ...grpc.CallOption) (*FrontendInfo, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(FrontendInfo) + err := c.cc.Invoke(ctx, Maglev_GetFrontend_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *maglevClient) ListBackends(ctx context.Context, in *ListBackendsRequest, opts ...grpc.CallOption) (*ListBackendsResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ListBackendsResponse) + err := c.cc.Invoke(ctx, Maglev_ListBackends_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *maglevClient) GetBackend(ctx context.Context, in *GetBackendRequest, opts ...grpc.CallOption) (*BackendInfo, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(BackendInfo) + err := c.cc.Invoke(ctx, Maglev_GetBackend_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *maglevClient) PauseBackend(ctx context.Context, in *PauseResumeRequest, opts ...grpc.CallOption) (*BackendInfo, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(BackendInfo) + err := c.cc.Invoke(ctx, Maglev_PauseBackend_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *maglevClient) ResumeBackend(ctx context.Context, in *PauseResumeRequest, opts ...grpc.CallOption) (*BackendInfo, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(BackendInfo) + err := c.cc.Invoke(ctx, Maglev_ResumeBackend_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *maglevClient) ListHealthChecks(ctx context.Context, in *ListHealthChecksRequest, opts ...grpc.CallOption) (*ListHealthChecksResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ListHealthChecksResponse) + err := c.cc.Invoke(ctx, Maglev_ListHealthChecks_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *maglevClient) GetHealthCheck(ctx context.Context, in *GetHealthCheckRequest, opts ...grpc.CallOption) (*HealthCheckInfo, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(HealthCheckInfo) + err := c.cc.Invoke(ctx, Maglev_GetHealthCheck_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *maglevClient) WatchBackendEvents(ctx context.Context, in *WatchRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[BackendEvent], error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + stream, err := c.cc.NewStream(ctx, &Maglev_ServiceDesc.Streams[0], Maglev_WatchBackendEvents_FullMethodName, cOpts...) + if err != nil { + return nil, err + } + x := &grpc.GenericClientStream[WatchRequest, BackendEvent]{ClientStream: stream} + if err := x.ClientStream.SendMsg(in); err != nil { + return nil, err + } + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + return x, nil +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type Maglev_WatchBackendEventsClient = grpc.ServerStreamingClient[BackendEvent] + +// MaglevServer is the server API for Maglev service. +// All implementations must embed UnimplementedMaglevServer +// for forward compatibility. +// +// Maglev exposes the state of backend health for all frontends. +type MaglevServer interface { + ListFrontends(context.Context, *ListFrontendsRequest) (*ListFrontendsResponse, error) + GetFrontend(context.Context, *GetFrontendRequest) (*FrontendInfo, error) + ListBackends(context.Context, *ListBackendsRequest) (*ListBackendsResponse, error) + GetBackend(context.Context, *GetBackendRequest) (*BackendInfo, error) + PauseBackend(context.Context, *PauseResumeRequest) (*BackendInfo, error) + ResumeBackend(context.Context, *PauseResumeRequest) (*BackendInfo, error) + ListHealthChecks(context.Context, *ListHealthChecksRequest) (*ListHealthChecksResponse, error) + GetHealthCheck(context.Context, *GetHealthCheckRequest) (*HealthCheckInfo, error) + WatchBackendEvents(*WatchRequest, grpc.ServerStreamingServer[BackendEvent]) error + mustEmbedUnimplementedMaglevServer() +} + +// UnimplementedMaglevServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedMaglevServer struct{} + +func (UnimplementedMaglevServer) ListFrontends(context.Context, *ListFrontendsRequest) (*ListFrontendsResponse, error) { + return nil, status.Error(codes.Unimplemented, "method ListFrontends not implemented") +} +func (UnimplementedMaglevServer) GetFrontend(context.Context, *GetFrontendRequest) (*FrontendInfo, error) { + return nil, status.Error(codes.Unimplemented, "method GetFrontend not implemented") +} +func (UnimplementedMaglevServer) ListBackends(context.Context, *ListBackendsRequest) (*ListBackendsResponse, error) { + return nil, status.Error(codes.Unimplemented, "method ListBackends not implemented") +} +func (UnimplementedMaglevServer) GetBackend(context.Context, *GetBackendRequest) (*BackendInfo, error) { + return nil, status.Error(codes.Unimplemented, "method GetBackend not implemented") +} +func (UnimplementedMaglevServer) PauseBackend(context.Context, *PauseResumeRequest) (*BackendInfo, error) { + return nil, status.Error(codes.Unimplemented, "method PauseBackend not implemented") +} +func (UnimplementedMaglevServer) ResumeBackend(context.Context, *PauseResumeRequest) (*BackendInfo, error) { + return nil, status.Error(codes.Unimplemented, "method ResumeBackend not implemented") +} +func (UnimplementedMaglevServer) ListHealthChecks(context.Context, *ListHealthChecksRequest) (*ListHealthChecksResponse, error) { + return nil, status.Error(codes.Unimplemented, "method ListHealthChecks not implemented") +} +func (UnimplementedMaglevServer) GetHealthCheck(context.Context, *GetHealthCheckRequest) (*HealthCheckInfo, error) { + return nil, status.Error(codes.Unimplemented, "method GetHealthCheck not implemented") +} +func (UnimplementedMaglevServer) WatchBackendEvents(*WatchRequest, grpc.ServerStreamingServer[BackendEvent]) error { + return status.Error(codes.Unimplemented, "method WatchBackendEvents not implemented") +} +func (UnimplementedMaglevServer) mustEmbedUnimplementedMaglevServer() {} +func (UnimplementedMaglevServer) testEmbeddedByValue() {} + +// UnsafeMaglevServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to MaglevServer will +// result in compilation errors. +type UnsafeMaglevServer interface { + mustEmbedUnimplementedMaglevServer() +} + +func RegisterMaglevServer(s grpc.ServiceRegistrar, srv MaglevServer) { + // If the following call panics, it indicates UnimplementedMaglevServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&Maglev_ServiceDesc, srv) +} + +func _Maglev_ListFrontends_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListFrontendsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MaglevServer).ListFrontends(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Maglev_ListFrontends_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MaglevServer).ListFrontends(ctx, req.(*ListFrontendsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Maglev_GetFrontend_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetFrontendRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MaglevServer).GetFrontend(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Maglev_GetFrontend_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MaglevServer).GetFrontend(ctx, req.(*GetFrontendRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Maglev_ListBackends_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListBackendsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MaglevServer).ListBackends(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Maglev_ListBackends_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MaglevServer).ListBackends(ctx, req.(*ListBackendsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Maglev_GetBackend_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetBackendRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MaglevServer).GetBackend(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Maglev_GetBackend_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MaglevServer).GetBackend(ctx, req.(*GetBackendRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Maglev_PauseBackend_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(PauseResumeRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MaglevServer).PauseBackend(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Maglev_PauseBackend_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MaglevServer).PauseBackend(ctx, req.(*PauseResumeRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Maglev_ResumeBackend_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(PauseResumeRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MaglevServer).ResumeBackend(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Maglev_ResumeBackend_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MaglevServer).ResumeBackend(ctx, req.(*PauseResumeRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Maglev_ListHealthChecks_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListHealthChecksRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MaglevServer).ListHealthChecks(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Maglev_ListHealthChecks_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MaglevServer).ListHealthChecks(ctx, req.(*ListHealthChecksRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Maglev_GetHealthCheck_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetHealthCheckRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MaglevServer).GetHealthCheck(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Maglev_GetHealthCheck_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MaglevServer).GetHealthCheck(ctx, req.(*GetHealthCheckRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Maglev_WatchBackendEvents_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(WatchRequest) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(MaglevServer).WatchBackendEvents(m, &grpc.GenericServerStream[WatchRequest, BackendEvent]{ServerStream: stream}) +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type Maglev_WatchBackendEventsServer = grpc.ServerStreamingServer[BackendEvent] + +// 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) +var Maglev_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "maglev.Maglev", + HandlerType: (*MaglevServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "ListFrontends", + Handler: _Maglev_ListFrontends_Handler, + }, + { + MethodName: "GetFrontend", + Handler: _Maglev_GetFrontend_Handler, + }, + { + MethodName: "ListBackends", + Handler: _Maglev_ListBackends_Handler, + }, + { + MethodName: "GetBackend", + Handler: _Maglev_GetBackend_Handler, + }, + { + MethodName: "PauseBackend", + Handler: _Maglev_PauseBackend_Handler, + }, + { + MethodName: "ResumeBackend", + Handler: _Maglev_ResumeBackend_Handler, + }, + { + MethodName: "ListHealthChecks", + Handler: _Maglev_ListHealthChecks_Handler, + }, + { + MethodName: "GetHealthCheck", + Handler: _Maglev_GetHealthCheck_Handler, + }, + }, + Streams: []grpc.StreamDesc{ + { + StreamName: "WatchBackendEvents", + Handler: _Maglev_WatchBackendEvents_Handler, + ServerStreams: true, + }, + }, + Metadata: "proto/maglev.proto", +} diff --git a/internal/grpcapi/server.go b/internal/grpcapi/server.go index 396c00b..2c32853 100644 --- a/internal/grpcapi/server.go +++ b/internal/grpcapi/server.go @@ -2,7 +2,6 @@ package grpcapi import ( "context" - "fmt" "net" "google.golang.org/grpc/codes" @@ -13,9 +12,9 @@ import ( "git.ipng.ch/ipng/vpp-maglev/internal/health" ) -// Server implements the HealthCheckerServer gRPC interface. +// Server implements the MaglevServer gRPC interface. type Server struct { - UnimplementedHealthCheckerServer + UnimplementedMaglevServer checker *checker.Checker } @@ -24,85 +23,88 @@ func NewServer(c *checker.Checker) *Server { return &Server{checker: c} } -// ListVIPs returns the names of all configured VIPs. -func (s *Server) ListVIPs(_ context.Context, _ *ListVIPsRequest) (*ListVIPsResponse, error) { - return &ListVIPsResponse{VipNames: s.checker.ListVIPs()}, nil +// ListFrontends returns the names of all configured frontends. +func (s *Server) ListFrontends(_ context.Context, _ *ListFrontendsRequest) (*ListFrontendsResponse, error) { + return &ListFrontendsResponse{FrontendNames: s.checker.ListFrontends()}, nil } -// GetVIP returns configuration details for a single VIP. -func (s *Server) GetVIP(_ context.Context, req *GetVIPRequest) (*VIPInfo, error) { - vip, ok := s.checker.GetVIP(req.VipName) +// GetFrontend returns configuration details for a single frontend. +func (s *Server) GetFrontend(_ context.Context, req *GetFrontendRequest) (*FrontendInfo, error) { + fe, ok := s.checker.GetFrontend(req.Name) if !ok { - return nil, status.Errorf(codes.NotFound, "vip %q not found", req.VipName) + return nil, status.Errorf(codes.NotFound, "frontend %q not found", req.Name) } - return vipToProto(req.VipName, vip), nil + return frontendToProto(req.Name, fe), nil } -// ListBackends returns health state for all backends of a VIP. -func (s *Server) ListBackends(_ context.Context, req *ListBackendsRequest) (*ListBackendsResponse, error) { - if _, ok := s.checker.GetVIP(req.VipName); !ok { - return nil, status.Errorf(codes.NotFound, "vip %q not found", req.VipName) - } - backends := s.checker.ListBackends(req.VipName) - resp := &ListBackendsResponse{} - for _, b := range backends { - resp.Backends = append(resp.Backends, backendToProto(b)) - } - return resp, nil +// ListBackends returns the names of all active backends. +func (s *Server) ListBackends(_ context.Context, _ *ListBackendsRequest) (*ListBackendsResponse, error) { + return &ListBackendsResponse{BackendNames: s.checker.ListBackends()}, nil } -// GetBackend returns health state for a specific VIP:backend tuple. +// GetBackend returns health state for a backend by name. func (s *Server) GetBackend(_ context.Context, req *GetBackendRequest) (*BackendInfo, error) { - b, ok := s.checker.GetBackend(req.VipName, req.BackendAddress) + b, ok := s.checker.GetBackend(req.Name) if !ok { - return nil, status.Errorf(codes.NotFound, "backend %q in vip %q not found", - req.BackendAddress, req.VipName) + return nil, status.Errorf(codes.NotFound, "backend %q not found", req.Name) } return backendToProto(b), nil } -// PauseBackend pauses health checking for a specific backend. +// PauseBackend pauses health checking for a backend by name. func (s *Server) PauseBackend(_ context.Context, req *PauseResumeRequest) (*BackendInfo, error) { - b, ok := s.checker.PauseBackend(req.VipName, req.BackendAddress) + b, ok := s.checker.PauseBackend(req.Name) if !ok { - return nil, status.Errorf(codes.NotFound, "backend %q in vip %q not found", - req.BackendAddress, req.VipName) + return nil, status.Errorf(codes.NotFound, "backend %q not found", req.Name) } return backendToProto(b), nil } -// ResumeBackend resumes health checking for a specific backend. +// ResumeBackend resumes health checking for a backend by name. func (s *Server) ResumeBackend(_ context.Context, req *PauseResumeRequest) (*BackendInfo, error) { - b, ok := s.checker.ResumeBackend(req.VipName, req.BackendAddress) + b, ok := s.checker.ResumeBackend(req.Name) if !ok { - return nil, status.Errorf(codes.NotFound, "backend %q in vip %q not found", - req.BackendAddress, req.VipName) + return nil, status.Errorf(codes.NotFound, "backend %q not found", req.Name) } return backendToProto(b), nil } -// WatchTransitions streams the current state of all backends on connect, then +// ListHealthChecks returns the names of all configured health checks. +func (s *Server) ListHealthChecks(_ context.Context, _ *ListHealthChecksRequest) (*ListHealthChecksResponse, error) { + return &ListHealthChecksResponse{Names: s.checker.ListHealthChecks()}, nil +} + +// GetHealthCheck returns the full configuration for a health check by name. +func (s *Server) GetHealthCheck(_ context.Context, req *GetHealthCheckRequest) (*HealthCheckInfo, error) { + hc, ok := s.checker.GetHealthCheck(req.Name) + if !ok { + return nil, status.Errorf(codes.NotFound, "healthcheck %q not found", req.Name) + } + return healthCheckToProto(req.Name, hc), nil +} + +// WatchBackendEvents streams the current state of all backends on connect, then // streams live state transitions until the client disconnects. -func (s *Server) WatchTransitions(_ *WatchRequest, stream HealthChecker_WatchTransitionsServer) error { +func (s *Server) WatchBackendEvents(_ *WatchRequest, stream Maglev_WatchBackendEventsServer) error { // Send current state of all backends as synthetic events. - for _, vipName := range s.checker.ListVIPs() { - for _, b := range s.checker.ListBackends(vipName) { - ev := &TransitionEvent{ - VipName: vipName, - BackendAddress: b.Address.String(), - Transition: &TransitionRecord{ - From: b.State.String(), - To: b.State.String(), - AtUnixNs: 0, - }, - } - if err := stream.Send(ev); err != nil { - return err - } + for _, name := range s.checker.ListBackends() { + snap, ok := s.checker.GetBackend(name) + if !ok { + continue + } + ev := &BackendEvent{ + BackendName: name, + Transition: &TransitionRecord{ + From: snap.Health.State.String(), + To: snap.Health.State.String(), + AtUnixNs: 0, + }, + } + if err := stream.Send(ev); err != nil { + return err } } - // Subscribe to live transitions. ch, unsub := s.checker.Subscribe() defer unsub() @@ -114,10 +116,9 @@ func (s *Server) WatchTransitions(_ *WatchRequest, stream HealthChecker_WatchTra if !ok { return nil } - ev := &TransitionEvent{ - VipName: e.VIPName, - BackendAddress: e.Backend.String(), - Transition: transitionToProto(e.Transition), + ev := &BackendEvent{ + BackendName: e.BackendName, + Transition: transitionToProto(e.Transition), } if err := stream.Send(ev); err != nil { return err @@ -128,28 +129,71 @@ func (s *Server) WatchTransitions(_ *WatchRequest, stream HealthChecker_WatchTra // ---- conversion helpers ---------------------------------------------------- -func vipToProto(name string, v config.VIP) *VIPInfo { - info := &VIPInfo{ - Name: name, - Address: v.Address.String(), - Protocol: v.Protocol, - Port: uint32(v.Port), - Description: v.Description, +func frontendToProto(name string, fe config.Frontend) *FrontendInfo { + return &FrontendInfo{ + Name: name, + Address: fe.Address.String(), + Protocol: fe.Protocol, + Port: uint32(fe.Port), + Description: fe.Description, + BackendNames: fe.Backends, } - for _, b := range v.Backends { - info.Backends = append(info.Backends, b.String()) +} + +func backendToProto(snap checker.BackendSnapshot) *BackendInfo { + info := &BackendInfo{ + Name: snap.Health.Name, + Address: snap.Health.Address.String(), + State: snap.Health.State.String(), + Enabled: snap.Config.Enabled, + Weight: int32(snap.Config.Weight), + Healthcheck: snap.Config.HealthCheck, + } + for _, t := range snap.Health.Transitions { + info.Transitions = append(info.Transitions, transitionToProto(t)) } return info } -func backendToProto(b *health.Backend) *BackendInfo { - info := &BackendInfo{ - VipName: b.VIPName, - Address: b.Address.String(), - State: b.State.String(), +func healthCheckToProto(name string, hc config.HealthCheck) *HealthCheckInfo { + info := &HealthCheckInfo{ + Name: name, + Type: hc.Type, + Port: uint32(hc.Port), + IntervalNs: hc.Interval.Nanoseconds(), + FastIntervalNs: hc.FastInterval.Nanoseconds(), + DownIntervalNs: hc.DownInterval.Nanoseconds(), + TimeoutNs: hc.Timeout.Nanoseconds(), + Rise: int32(hc.Rise), + Fall: int32(hc.Fall), } - for _, t := range b.Transitions { - info.Transitions = append(info.Transitions, transitionToProto(t)) + if hc.ProbeIPv4Src != nil { + info.ProbeIpv4Src = hc.ProbeIPv4Src.String() + } + if hc.ProbeIPv6Src != nil { + info.ProbeIpv6Src = hc.ProbeIPv6Src.String() + } + if hc.HTTP != nil { + re := "" + if hc.HTTP.ResponseRegexp != nil { + re = hc.HTTP.ResponseRegexp.String() + } + info.Http = &HTTPCheckParams{ + Path: hc.HTTP.Path, + Host: hc.HTTP.Host, + ResponseCodeMin: int32(hc.HTTP.ResponseCodeMin), + ResponseCodeMax: int32(hc.HTTP.ResponseCodeMax), + ResponseRegexp: re, + ServerName: hc.HTTP.ServerName, + InsecureSkipVerify: hc.HTTP.InsecureSkipVerify, + } + } + if hc.TCP != nil { + info.Tcp = &TCPCheckParams{ + Ssl: hc.TCP.SSL, + ServerName: hc.TCP.ServerName, + InsecureSkipVerify: hc.TCP.InsecureSkipVerify, + } } return info } @@ -164,4 +208,3 @@ func transitionToProto(t health.Transition) *TransitionRecord { // Ensure net.IP is imported (used via b.Address.String()). var _ = net.IP{} -var _ = fmt.Sprintf diff --git a/internal/grpcapi/server_test.go b/internal/grpcapi/server_test.go index 0151ad1..b20d9be 100644 --- a/internal/grpcapi/server_test.go +++ b/internal/grpcapi/server_test.go @@ -15,22 +15,31 @@ import ( ) func makeTestChecker(ctx context.Context) *checker.Checker { - cfg := &config.Frontend{ - HealthCheckNetns: "test", - HealthChecker: config.HealthCheckerConfig{TransitionHistory: 5}, - VIPs: map[string]config.VIP{ + cfg := &config.Config{ + HealthChecker: config.HealthCheckerConfig{TransitionHistory: 5}, + HealthChecks: map[string]config.HealthCheck{ + "icmp": { + Type: "icmp", + Interval: time.Hour, // long interval: probes won't fire during tests + Timeout: time.Second, + Fall: 3, + Rise: 2, + }, + }, + Backends: map[string]config.Backend{ + "be0": { + Address: net.ParseIP("10.0.0.2"), + HealthCheck: "icmp", + Enabled: true, + Weight: 100, + }, + }, + Frontends: map[string]config.Frontend{ "web": { Address: net.ParseIP("192.0.2.1"), Protocol: "tcp", Port: 80, - Backends: []net.IP{net.ParseIP("10.0.0.2")}, - HealthCheck: config.HealthCheck{ - Type: "icmp", - Interval: time.Hour, // long interval: probes won't fire during tests - Timeout: time.Second, - Fall: 3, - Rise: 2, - }, + Backends: []string{"be0"}, }, }, } @@ -41,14 +50,14 @@ func makeTestChecker(ctx context.Context) *checker.Checker { return c } -func startTestServer(t *testing.T, c *checker.Checker) (HealthCheckerClient, func()) { +func startTestServer(t *testing.T, c *checker.Checker) (MaglevClient, func()) { t.Helper() lis, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { t.Fatalf("listen: %v", err) } srv := grpc.NewServer() - RegisterHealthCheckerServer(srv, NewServer(c)) + RegisterMaglevServer(srv, NewServer(c)) go srv.Serve(lis) //nolint:errcheck conn, err := grpc.NewClient(lis.Addr().String(), @@ -56,13 +65,13 @@ func startTestServer(t *testing.T, c *checker.Checker) (HealthCheckerClient, fun if err != nil { t.Fatalf("dial: %v", err) } - return NewHealthCheckerClient(conn), func() { + return NewMaglevClient(conn), func() { conn.Close() srv.Stop() } } -func TestListVIPs(t *testing.T) { +func TestListFrontends(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -70,16 +79,16 @@ func TestListVIPs(t *testing.T) { client, cleanup := startTestServer(t, c) defer cleanup() - resp, err := client.ListVIPs(ctx, &ListVIPsRequest{}) + resp, err := client.ListFrontends(ctx, &ListFrontendsRequest{}) if err != nil { - t.Fatalf("ListVIPs: %v", err) + t.Fatalf("ListFrontends: %v", err) } - if len(resp.VipNames) != 1 || resp.VipNames[0] != "web" { - t.Errorf("ListVIPs: got %v, want [web]", resp.VipNames) + if len(resp.FrontendNames) != 1 || resp.FrontendNames[0] != "web" { + t.Errorf("ListFrontends: got %v, want [web]", resp.FrontendNames) } } -func TestGetVIP(t *testing.T) { +func TestGetFrontend(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -87,19 +96,22 @@ func TestGetVIP(t *testing.T) { client, cleanup := startTestServer(t, c) defer cleanup() - info, err := client.GetVIP(ctx, &GetVIPRequest{VipName: "web"}) + info, err := client.GetFrontend(ctx, &GetFrontendRequest{Name:"web"}) if err != nil { - t.Fatalf("GetVIP: %v", err) + t.Fatalf("GetFrontend: %v", err) } if info.Address != "192.0.2.1" { - t.Errorf("GetVIP address: got %q, want 192.0.2.1", info.Address) + t.Errorf("GetFrontend address: got %q, want 192.0.2.1", info.Address) } if info.Port != 80 { - t.Errorf("GetVIP port: got %d, want 80", info.Port) + t.Errorf("GetFrontend port: got %d, want 80", info.Port) + } + if len(info.BackendNames) != 1 || info.BackendNames[0] != "be0" { + t.Errorf("GetFrontend backend_names: got %v, want [be0]", info.BackendNames) } } -func TestGetVIPNotFound(t *testing.T) { +func TestGetFrontendNotFound(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -107,9 +119,26 @@ func TestGetVIPNotFound(t *testing.T) { client, cleanup := startTestServer(t, c) defer cleanup() - _, err := client.GetVIP(ctx, &GetVIPRequest{VipName: "nope"}) + _, err := client.GetFrontend(ctx, &GetFrontendRequest{Name:"nope"}) if err == nil { - t.Error("expected error for unknown VIP") + t.Error("expected error for unknown frontend") + } +} + +func TestListBackends(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + c := makeTestChecker(ctx) + client, cleanup := startTestServer(t, c) + defer cleanup() + + resp, err := client.ListBackends(ctx, &ListBackendsRequest{}) + if err != nil { + t.Fatalf("ListBackends: %v", err) + } + if len(resp.BackendNames) != 1 || resp.BackendNames[0] != "be0" { + t.Errorf("ListBackends: got %v, want [be0]", resp.BackendNames) } } @@ -121,16 +150,36 @@ func TestGetBackend(t *testing.T) { client, cleanup := startTestServer(t, c) defer cleanup() - info, err := client.GetBackend(ctx, &GetBackendRequest{ - VipName: "web", - BackendAddress: "10.0.0.2", - }) + info, err := client.GetBackend(ctx, &GetBackendRequest{Name:"be0"}) if err != nil { t.Fatalf("GetBackend: %v", err) } if info.State != health.StateUnknown.String() { t.Errorf("initial state: got %q, want unknown", info.State) } + if !info.Enabled { + t.Error("expected enabled=true") + } + if info.Weight != 100 { + t.Errorf("weight: got %d, want 100", info.Weight) + } + if info.Healthcheck != "icmp" { + t.Errorf("healthcheck: got %q, want icmp", info.Healthcheck) + } +} + +func TestGetBackendNotFound(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + c := makeTestChecker(ctx) + client, cleanup := startTestServer(t, c) + defer cleanup() + + _, err := client.GetBackend(ctx, &GetBackendRequest{Name:"nope"}) + if err == nil { + t.Error("expected error for unknown backend") + } } func TestPauseResumeBackend(t *testing.T) { @@ -141,10 +190,7 @@ func TestPauseResumeBackend(t *testing.T) { client, cleanup := startTestServer(t, c) defer cleanup() - info, err := client.PauseBackend(ctx, &PauseResumeRequest{ - VipName: "web", - BackendAddress: "10.0.0.2", - }) + info, err := client.PauseBackend(ctx, &PauseResumeRequest{Name:"be0"}) if err != nil { t.Fatalf("PauseBackend: %v", err) } @@ -152,10 +198,7 @@ func TestPauseResumeBackend(t *testing.T) { t.Errorf("after pause: got %q, want paused", info.State) } - info, err = client.ResumeBackend(ctx, &PauseResumeRequest{ - VipName: "web", - BackendAddress: "10.0.0.2", - }) + info, err = client.ResumeBackend(ctx, &PauseResumeRequest{Name:"be0"}) if err != nil { t.Fatalf("ResumeBackend: %v", err) } @@ -164,7 +207,7 @@ func TestPauseResumeBackend(t *testing.T) { } } -func TestWatchTransitions(t *testing.T) { +func TestListHealthChecks(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -172,17 +215,68 @@ func TestWatchTransitions(t *testing.T) { client, cleanup := startTestServer(t, c) defer cleanup() - stream, err := client.WatchTransitions(ctx, &WatchRequest{}) + resp, err := client.ListHealthChecks(ctx, &ListHealthChecksRequest{}) if err != nil { - t.Fatalf("WatchTransitions: %v", err) + t.Fatalf("ListHealthChecks: %v", err) + } + if len(resp.Names) != 1 || resp.Names[0] != "icmp" { + t.Errorf("ListHealthChecks: got %v, want [icmp]", resp.Names) + } +} + +func TestGetHealthCheck(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + c := makeTestChecker(ctx) + client, cleanup := startTestServer(t, c) + defer cleanup() + + info, err := client.GetHealthCheck(ctx, &GetHealthCheckRequest{Name: "icmp"}) + if err != nil { + t.Fatalf("GetHealthCheck: %v", err) + } + if info.Type != "icmp" { + t.Errorf("type: got %q, want icmp", info.Type) + } + if info.Fall != 3 || info.Rise != 2 { + t.Errorf("fall/rise: got %d/%d, want 3/2", info.Fall, info.Rise) + } +} + +func TestGetHealthCheckNotFound(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + c := makeTestChecker(ctx) + client, cleanup := startTestServer(t, c) + defer cleanup() + + _, err := client.GetHealthCheck(ctx, &GetHealthCheckRequest{Name: "nope"}) + if err == nil { + t.Error("expected error for unknown healthcheck") + } +} + +func TestWatchBackendEvents(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + c := makeTestChecker(ctx) + client, cleanup := startTestServer(t, c) + defer cleanup() + + stream, err := client.WatchBackendEvents(ctx, &WatchRequest{}) + if err != nil { + t.Fatalf("WatchBackendEvents: %v", err) } - // Should receive the current state for web:10.0.0.2 immediately. + // Should receive the current state for be0 immediately. ev, err := stream.Recv() if err != nil { t.Fatalf("Recv: %v", err) } - if ev.VipName != "web" || ev.BackendAddress != "10.0.0.2" { - t.Errorf("initial event: vip=%q backend=%q", ev.VipName, ev.BackendAddress) + if ev.BackendName != "be0" { + t.Errorf("initial event: backend=%q, want be0", ev.BackendName) } } diff --git a/internal/health/state.go b/internal/health/state.go index ecb4e1b..9ccfaac 100644 --- a/internal/health/state.go +++ b/internal/health/state.go @@ -90,9 +90,9 @@ func (h *HealthCounter) RecordFail() bool { return !wasDown && !h.IsUp() } -// Backend tracks the health state of one VIP:backend tuple. +// Backend tracks the health state of a named backend. type Backend struct { - VIPName string + Name string Address net.IP State State Counter HealthCounter @@ -100,9 +100,9 @@ type Backend struct { } // New creates a Backend in StateUnknown. -func New(vipName string, addr net.IP, rise, fall int) *Backend { +func New(name string, addr net.IP, rise, fall int) *Backend { return &Backend{ - VIPName: vipName, + Name: name, Address: addr, State: StateUnknown, Counter: HealthCounter{Rise: rise, Fall: fall}, diff --git a/proto/healthchecker.proto b/proto/healthchecker.proto deleted file mode 100644 index 74ad24c..0000000 --- a/proto/healthchecker.proto +++ /dev/null @@ -1,78 +0,0 @@ -syntax = "proto3"; - -package healthchecker; - -option go_package = "git.ipng.ch/ipng/vpp-maglev/internal/grpcapi"; - -// HealthChecker exposes the state of backend health for all VIPs. -service HealthChecker { - rpc ListVIPs(ListVIPsRequest) returns (ListVIPsResponse); - rpc GetVIP(GetVIPRequest) returns (VIPInfo); - rpc ListBackends(ListBackendsRequest) returns (ListBackendsResponse); - rpc GetBackend(GetBackendRequest) returns (BackendInfo); - rpc PauseBackend(PauseResumeRequest) returns (BackendInfo); - rpc ResumeBackend(PauseResumeRequest) returns (BackendInfo); - rpc WatchTransitions(WatchRequest) returns (stream TransitionEvent); -} - -// ---- requests --------------------------------------------------------------- - -message ListVIPsRequest {} - -message GetVIPRequest { - string vip_name = 1; -} - -message ListBackendsRequest { - string vip_name = 1; -} - -message GetBackendRequest { - string vip_name = 1; - string backend_address = 2; -} - -message PauseResumeRequest { - string vip_name = 1; - string backend_address = 2; -} - -message WatchRequest {} - -// ---- responses -------------------------------------------------------------- - -message ListVIPsResponse { - repeated string vip_names = 1; -} - -message VIPInfo { - string name = 1; - string address = 2; - string protocol = 3; - uint32 port = 4; - repeated string backends = 5; - string description = 6; -} - -message ListBackendsResponse { - repeated BackendInfo backends = 1; -} - -message BackendInfo { - string vip_name = 1; - string address = 2; - string state = 3; - repeated TransitionRecord transitions = 4; -} - -message TransitionRecord { - string from = 1; - string to = 2; - int64 at_unix_ns = 3; -} - -message TransitionEvent { - string vip_name = 1; - string backend_address = 2; - TransitionRecord transition = 3; -} diff --git a/proto/maglev.proto b/proto/maglev.proto new file mode 100644 index 0000000..a4c3b95 --- /dev/null +++ b/proto/maglev.proto @@ -0,0 +1,120 @@ +syntax = "proto3"; + +package maglev; + +option go_package = "git.ipng.ch/ipng/vpp-maglev/internal/grpcapi"; + +// Maglev exposes the state of backend health for all frontends. +service Maglev { + rpc ListFrontends(ListFrontendsRequest) returns (ListFrontendsResponse); + rpc GetFrontend(GetFrontendRequest) returns (FrontendInfo); + rpc ListBackends(ListBackendsRequest) returns (ListBackendsResponse); + rpc GetBackend(GetBackendRequest) returns (BackendInfo); + rpc PauseBackend(PauseResumeRequest) returns (BackendInfo); + rpc ResumeBackend(PauseResumeRequest) returns (BackendInfo); + rpc ListHealthChecks(ListHealthChecksRequest) returns (ListHealthChecksResponse); + rpc GetHealthCheck(GetHealthCheckRequest) returns (HealthCheckInfo); + rpc WatchBackendEvents(WatchRequest) returns (stream BackendEvent); +} + +// ---- requests --------------------------------------------------------------- + +message ListFrontendsRequest {} + +message GetFrontendRequest { + string name = 1; +} + +message ListBackendsRequest {} + +message GetBackendRequest { + string name = 1; +} + +message PauseResumeRequest { + string name = 1; +} + +message ListHealthChecksRequest {} + +message GetHealthCheckRequest { + string name = 1; +} + +message WatchRequest {} + +// ---- responses -------------------------------------------------------------- + +message ListFrontendsResponse { + repeated string frontend_names = 1; +} + +message FrontendInfo { + string name = 1; + string address = 2; + string protocol = 3; + uint32 port = 4; + repeated string backend_names = 5; + string description = 6; +} + +message ListBackendsResponse { + repeated string backend_names = 1; +} + +message ListHealthChecksResponse { + repeated string names = 1; +} + +message HTTPCheckParams { + string path = 1; + string host = 2; + int32 response_code_min = 3; + int32 response_code_max = 4; + string response_regexp = 5; + string server_name = 6; + bool insecure_skip_verify = 7; +} + +message TCPCheckParams { + bool ssl = 1; + string server_name = 2; + bool insecure_skip_verify = 3; +} + +message HealthCheckInfo { + string name = 1; + string type = 2; + uint32 port = 3; + string probe_ipv4_src = 4; + string probe_ipv6_src = 5; + int64 interval_ns = 6; + int64 fast_interval_ns = 7; + int64 down_interval_ns = 8; + int64 timeout_ns = 9; + int32 rise = 10; + int32 fall = 11; + HTTPCheckParams http = 12; + TCPCheckParams tcp = 13; +} + +message BackendInfo { + string name = 1; + string address = 2; + string state = 3; + repeated TransitionRecord transitions = 4; + bool enabled = 5; + int32 weight = 6; + string healthcheck = 7; +} + +message TransitionRecord { + string from = 1; + string to = 2; + int64 at_unix_ns = 3; +} + +message BackendEvent { + string backend_name = 1; + TransitionRecord transition = 2; +}