Revision: Rename to 'maglevd'; Refactor config structure
This commit is contained in:
@@ -14,14 +14,13 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
|||||||
iproute2 \
|
iproute2 \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& 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:
|
# 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.:
|
# 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
|
# or in Kubernetes via securityContext.capabilities.add
|
||||||
|
|
||||||
ENTRYPOINT ["/usr/local/bin/healthchecker"]
|
ENTRYPOINT ["/usr/local/bin/maglevd"]
|
||||||
|
|||||||
8
Makefile
8
Makefile
@@ -1,15 +1,15 @@
|
|||||||
BINARY := healthchecker
|
BINARY := maglevd
|
||||||
MODULE := git.ipng.ch/ipng/vpp-maglev
|
MODULE := git.ipng.ch/ipng/vpp-maglev
|
||||||
PROTO_DIR := proto
|
PROTO_DIR := proto
|
||||||
PROTO_FILE := $(PROTO_DIR)/healthchecker.proto
|
PROTO_FILE := $(PROTO_DIR)/maglev.proto
|
||||||
GEN_FILES := internal/grpcapi/healthchecker.pb.go internal/grpcapi/healthchecker_grpc.pb.go
|
GEN_FILES := internal/grpcapi/maglev.pb.go internal/grpcapi/maglev_grpc.pb.go
|
||||||
|
|
||||||
.PHONY: all build test proto lint clean
|
.PHONY: all build test proto lint clean
|
||||||
|
|
||||||
all: build
|
all: build
|
||||||
|
|
||||||
build: $(GEN_FILES)
|
build: $(GEN_FILES)
|
||||||
go build -o bin/$(BINARY) ./cmd/$(BINARY)/
|
go build -o bin/$(BINARY) ./cmd/maglevd/
|
||||||
|
|
||||||
test: $(GEN_FILES)
|
test: $(GEN_FILES)
|
||||||
go test ./...
|
go test ./...
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ func run() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("load config: %w", err)
|
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 ------------------------------------------------------------
|
// ---- checker ------------------------------------------------------------
|
||||||
chkr := checker.New(cfg)
|
chkr := checker.New(cfg)
|
||||||
@@ -63,7 +63,7 @@ func run() error {
|
|||||||
return fmt.Errorf("listen %s: %w", *grpcAddr, err)
|
return fmt.Errorf("listen %s: %w", *grpcAddr, err)
|
||||||
}
|
}
|
||||||
srv := grpc.NewServer()
|
srv := grpc.NewServer()
|
||||||
grpcapi.RegisterHealthCheckerServer(srv, grpcapi.NewServer(chkr))
|
grpcapi.RegisterMaglevServer(srv, grpcapi.NewServer(chkr))
|
||||||
slog.Info("grpc-listening", "addr", *grpcAddr)
|
slog.Info("grpc-listening", "addr", *grpcAddr)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
@@ -89,7 +89,7 @@ func run() error {
|
|||||||
slog.Error("checker-reload-error", "err", err)
|
slog.Error("checker-reload-error", "err", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
slog.Info("config-reload-done", "vips", len(newCfg.VIPs))
|
slog.Info("config-reload-done", "frontends", len(newCfg.Frontends))
|
||||||
|
|
||||||
case syscall.SIGTERM, syscall.SIGINT:
|
case syscall.SIGTERM, syscall.SIGINT:
|
||||||
slog.Info("shutdown", "signal", sig)
|
slog.Info("shutdown", "signal", sig)
|
||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"net"
|
"net"
|
||||||
|
"sort"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -12,30 +13,35 @@ import (
|
|||||||
"git.ipng.ch/ipng/vpp-maglev/internal/prober"
|
"git.ipng.ch/ipng/vpp-maglev/internal/prober"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Event is emitted on every backend state transition.
|
// BackendSnapshot combines the live health state with the config entry for a backend.
|
||||||
type Event struct {
|
type BackendSnapshot struct {
|
||||||
VIPName string
|
Health *health.Backend
|
||||||
Backend net.IP
|
Config config.Backend
|
||||||
Transition health.Transition
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type backendKey struct {
|
// Event is emitted on every backend state transition, once per frontend that
|
||||||
VIPName string
|
// references the backend.
|
||||||
Backend string // net.IP.String()
|
type Event struct {
|
||||||
|
FrontendName string
|
||||||
|
BackendName string
|
||||||
|
Backend net.IP
|
||||||
|
Transition health.Transition
|
||||||
}
|
}
|
||||||
|
|
||||||
type worker struct {
|
type worker struct {
|
||||||
backend *health.Backend
|
backend *health.Backend
|
||||||
hc config.HealthCheck
|
hc config.HealthCheck
|
||||||
vip config.VIP
|
entry config.Backend
|
||||||
cancel context.CancelFunc
|
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 {
|
type Checker struct {
|
||||||
cfg *config.Frontend
|
cfg *config.Config
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
workers map[backendKey]*worker
|
workers map[string]*worker // keyed by backend name
|
||||||
|
|
||||||
subsMu sync.Mutex
|
subsMu sync.Mutex
|
||||||
nextID int
|
nextID int
|
||||||
@@ -44,10 +50,10 @@ type Checker struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// New creates a Checker. Call Run to start probing.
|
// New creates a Checker. Call Run to start probing.
|
||||||
func New(cfg *config.Frontend) *Checker {
|
func New(cfg *config.Config) *Checker {
|
||||||
return &Checker{
|
return &Checker{
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
workers: make(map[backendKey]*worker),
|
workers: make(map[string]*worker),
|
||||||
subs: make(map[int]chan Event),
|
subs: make(map[int]chan Event),
|
||||||
eventCh: make(chan Event, 256),
|
eventCh: make(chan Event, 256),
|
||||||
}
|
}
|
||||||
@@ -58,13 +64,11 @@ func (c *Checker) Run(ctx context.Context) error {
|
|||||||
go c.fanOut(ctx)
|
go c.fanOut(ctx)
|
||||||
|
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
total := totalBackends(c.cfg)
|
names := activeBackendNames(c.cfg)
|
||||||
pos := 0
|
for i, name := range names {
|
||||||
for vipName, vip := range c.cfg.VIPs {
|
b := c.cfg.Backends[name]
|
||||||
for _, backend := range vip.Backends {
|
hc := c.cfg.HealthChecks[b.HealthCheck]
|
||||||
c.startWorker(ctx, vipName, vip, backend, pos, total)
|
c.startWorker(ctx, name, b, hc, i, len(names))
|
||||||
pos++
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
|
|
||||||
@@ -73,55 +77,45 @@ func (c *Checker) Run(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Reload applies a new config without restarting the process.
|
// Reload applies a new config without restarting the process.
|
||||||
// New tuples are added, removed tuples are stopped, changed tuples are restarted.
|
// New backends are added, removed backends are stopped, changed backends are
|
||||||
// Existing tuples with unchanged healthcheck config continue uninterrupted.
|
// restarted. Backends whose healthcheck config is unchanged continue
|
||||||
func (c *Checker) Reload(ctx context.Context, cfg *config.Frontend) error {
|
// uninterrupted, even if the set of frontends referencing them changes.
|
||||||
|
func (c *Checker) Reload(ctx context.Context, cfg *config.Config) error {
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
defer c.mu.Unlock()
|
defer c.mu.Unlock()
|
||||||
|
|
||||||
type desired struct {
|
desired := map[string]struct{}{}
|
||||||
vipName string
|
for _, name := range activeBackendNames(cfg) {
|
||||||
vip config.VIP
|
desired[name] = struct{}{}
|
||||||
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}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop workers no longer in config.
|
// Stop workers no longer needed.
|
||||||
for key, w := range c.workers {
|
for name, w := range c.workers {
|
||||||
if _, ok := desiredMap[key]; !ok {
|
if _, ok := desired[name]; !ok {
|
||||||
slog.Info("backend-stop", "vip", key.VIPName, "backend", key.Backend)
|
slog.Info("backend-stop", "backend", name)
|
||||||
w.cancel()
|
w.cancel()
|
||||||
delete(c.workers, key)
|
delete(c.workers, name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add new or restart changed workers.
|
// Add new or restart changed workers.
|
||||||
total := len(desiredMap)
|
names := activeBackendNames(cfg)
|
||||||
pos := 0
|
for i, name := range names {
|
||||||
for key, d := range desiredMap {
|
b := cfg.Backends[name]
|
||||||
if w, ok := c.workers[key]; ok {
|
hc := cfg.HealthChecks[b.HealthCheck]
|
||||||
if healthCheckEqual(w.hc, d.vip.HealthCheck) {
|
if w, ok := c.workers[name]; ok {
|
||||||
pos++
|
if healthCheckEqual(w.hc, hc) {
|
||||||
|
// Update entry metadata (weight, etc.) in place without restart.
|
||||||
|
w.entry = b
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
slog.Info("backend-restart", "vip", key.VIPName, "backend", key.Backend)
|
slog.Info("backend-restart", "backend", name)
|
||||||
w.cancel()
|
w.cancel()
|
||||||
w.hc = d.vip.HealthCheck
|
c.startWorker(ctx, name, b, hc, i, len(names))
|
||||||
w.vip = d.vip
|
|
||||||
wCtx, cancel := context.WithCancel(ctx)
|
|
||||||
w.cancel = cancel
|
|
||||||
go c.runProbe(wCtx, key, 0, 1) // no stagger on reload
|
|
||||||
} else {
|
} else {
|
||||||
slog.Info("backend-start", "vip", d.vipName, "backend", d.backend)
|
slog.Info("backend-start", "backend", name)
|
||||||
c.startWorker(ctx, d.vipName, d.vip, d.backend, pos, total)
|
c.startWorker(ctx, name, b, hc, i, len(names))
|
||||||
}
|
}
|
||||||
pos++
|
|
||||||
}
|
}
|
||||||
|
|
||||||
c.cfg = cfg
|
c.cfg = cfg
|
||||||
@@ -145,109 +139,142 @@ func (c *Checker) Subscribe() (<-chan Event, func()) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListVIPs returns the names of all configured VIPs.
|
// ListFrontends returns the names of all configured frontends.
|
||||||
func (c *Checker) ListVIPs() []string {
|
func (c *Checker) ListFrontends() []string {
|
||||||
c.mu.RLock()
|
c.mu.RLock()
|
||||||
defer c.mu.RUnlock()
|
defer c.mu.RUnlock()
|
||||||
names := make([]string, 0, len(c.cfg.VIPs))
|
names := make([]string, 0, len(c.cfg.Frontends))
|
||||||
for name := range c.cfg.VIPs {
|
for name := range c.cfg.Frontends {
|
||||||
names = append(names, name)
|
names = append(names, name)
|
||||||
}
|
}
|
||||||
return names
|
return names
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetVIP returns the VIP config for the given name.
|
// GetFrontend returns the frontend config for the given name.
|
||||||
func (c *Checker) GetVIP(name string) (config.VIP, bool) {
|
func (c *Checker) GetFrontend(name string) (config.Frontend, bool) {
|
||||||
c.mu.RLock()
|
c.mu.RLock()
|
||||||
defer c.mu.RUnlock()
|
defer c.mu.RUnlock()
|
||||||
v, ok := c.cfg.VIPs[name]
|
v, ok := c.cfg.Frontends[name]
|
||||||
return v, ok
|
return v, ok
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListBackends returns the backend states for all backends of a VIP.
|
// ListHealthChecks returns the names of all configured health checks, sorted.
|
||||||
func (c *Checker) ListBackends(vipName string) []*health.Backend {
|
func (c *Checker) ListHealthChecks() []string {
|
||||||
c.mu.RLock()
|
c.mu.RLock()
|
||||||
defer c.mu.RUnlock()
|
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
|
var out []*health.Backend
|
||||||
for key, w := range c.workers {
|
for _, name := range fe.Backends {
|
||||||
if key.VIPName == vipName {
|
if w, ok := c.workers[name]; ok {
|
||||||
out = append(out, w.backend)
|
out = append(out, w.backend)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetBackend returns the backend state for a specific VIP:backend tuple.
|
// GetBackend returns a snapshot of the health state and config for a backend by name.
|
||||||
func (c *Checker) GetBackend(vipName, backendAddr string) (*health.Backend, bool) {
|
func (c *Checker) GetBackend(name string) (BackendSnapshot, bool) {
|
||||||
c.mu.RLock()
|
c.mu.RLock()
|
||||||
defer c.mu.RUnlock()
|
defer c.mu.RUnlock()
|
||||||
key := backendKey{VIPName: vipName, Backend: backendAddr}
|
w, ok := c.workers[name]
|
||||||
w, ok := c.workers[key]
|
|
||||||
if !ok {
|
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.
|
// PauseBackend pauses health checking for a backend by name.
|
||||||
func (c *Checker) PauseBackend(vipName, backendAddr string) (*health.Backend, bool) {
|
func (c *Checker) PauseBackend(name string) (BackendSnapshot, bool) {
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
defer c.mu.Unlock()
|
defer c.mu.Unlock()
|
||||||
key := backendKey{VIPName: vipName, Backend: backendAddr}
|
w, ok := c.workers[name]
|
||||||
w, ok := c.workers[key]
|
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, false
|
return BackendSnapshot{}, false
|
||||||
}
|
}
|
||||||
maxHistory := c.cfg.HealthChecker.TransitionHistory
|
maxHistory := c.cfg.HealthChecker.TransitionHistory
|
||||||
if w.backend.Pause(maxHistory) {
|
if w.backend.Pause(maxHistory) {
|
||||||
slog.Info("backend-pause", "vip", vipName, "backend", backendAddr)
|
slog.Info("backend-pause", "backend", name)
|
||||||
c.emit(Event{VIPName: vipName, Backend: w.backend.Address, Transition: w.backend.Transitions[0]})
|
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.
|
// ResumeBackend resumes health checking for a backend by name.
|
||||||
func (c *Checker) ResumeBackend(vipName, backendAddr string) (*health.Backend, bool) {
|
func (c *Checker) ResumeBackend(name string) (BackendSnapshot, bool) {
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
defer c.mu.Unlock()
|
defer c.mu.Unlock()
|
||||||
key := backendKey{VIPName: vipName, Backend: backendAddr}
|
w, ok := c.workers[name]
|
||||||
w, ok := c.workers[key]
|
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, false
|
return BackendSnapshot{}, false
|
||||||
}
|
}
|
||||||
maxHistory := c.cfg.HealthChecker.TransitionHistory
|
maxHistory := c.cfg.HealthChecker.TransitionHistory
|
||||||
if w.backend.Resume(maxHistory) {
|
if w.backend.Resume(maxHistory) {
|
||||||
slog.Info("backend-resume", "vip", vipName, "backend", backendAddr)
|
slog.Info("backend-resume", "backend", name)
|
||||||
c.emit(Event{VIPName: vipName, Backend: w.backend.Address, Transition: w.backend.Transitions[0]})
|
c.emitForBackend(name, w.backend.Address, w.backend.Transitions[0])
|
||||||
}
|
}
|
||||||
return w.backend, true
|
return BackendSnapshot{Health: w.backend, Config: w.entry}, true
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- internal --------------------------------------------------------------
|
// ---- internal --------------------------------------------------------------
|
||||||
|
|
||||||
// startWorker creates a Backend and launches a probe goroutine.
|
// 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.
|
// 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) {
|
func (c *Checker) startWorker(ctx context.Context, name string, entry config.Backend, hc config.HealthCheck, pos, total int) {
|
||||||
key := backendKey{VIPName: vipName, Backend: backend.String()}
|
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)
|
wCtx, cancel := context.WithCancel(ctx)
|
||||||
hc := vip.HealthCheck
|
|
||||||
w := &worker{
|
w := &worker{
|
||||||
backend: health.New(vipName, backend, hc.Rise, hc.Fall),
|
backend: health.New(name, entry.Address, rise, fall),
|
||||||
hc: hc,
|
hc: hc,
|
||||||
vip: vip,
|
entry: entry,
|
||||||
cancel: cancel,
|
cancel: cancel,
|
||||||
}
|
}
|
||||||
c.workers[key] = w
|
c.workers[name] = w
|
||||||
go c.runProbe(wCtx, key, pos, total)
|
go c.runProbe(wCtx, name, pos, total)
|
||||||
}
|
}
|
||||||
|
|
||||||
// runProbe is the per-backend probe loop.
|
// 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, name string, pos, total int) {
|
||||||
func (c *Checker) runProbe(ctx context.Context, key backendKey, pos, total int) {
|
|
||||||
// Stagger initial probe to spread startup load.
|
|
||||||
c.mu.RLock()
|
c.mu.RLock()
|
||||||
w, ok := c.workers[key]
|
w, ok := c.workers[name]
|
||||||
if !ok {
|
if !ok {
|
||||||
c.mu.RUnlock()
|
c.mu.RUnlock()
|
||||||
return
|
return
|
||||||
@@ -265,15 +292,21 @@ func (c *Checker) runProbe(ctx context.Context, key backendKey, pos, total int)
|
|||||||
|
|
||||||
for {
|
for {
|
||||||
c.mu.RLock()
|
c.mu.RLock()
|
||||||
w, ok := c.workers[key]
|
w, ok := c.workers[name]
|
||||||
if !ok {
|
if !ok {
|
||||||
c.mu.RUnlock()
|
c.mu.RUnlock()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
hc := w.hc
|
hc := w.hc
|
||||||
vip := w.vip
|
entry := w.entry
|
||||||
maxHistory := c.cfg.HealthChecker.TransitionHistory
|
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()
|
c.mu.RUnlock()
|
||||||
|
|
||||||
select {
|
select {
|
||||||
@@ -282,42 +315,44 @@ func (c *Checker) runProbe(ctx context.Context, key backendKey, pos, total int)
|
|||||||
case <-time.After(sleepFor):
|
case <-time.After(sleepFor):
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine source IP based on target address family.
|
var result health.ProbeResult
|
||||||
backendIP := net.ParseIP(key.Backend)
|
if entry.HealthCheck == "" {
|
||||||
var probeSrc net.IP
|
// No healthcheck configured: synthesise a passing result so the
|
||||||
if backendIP.To4() != nil {
|
// backend is assumed healthy without any network activity.
|
||||||
probeSrc = c.cfg.ProbeIPv4Src
|
result = health.ProbeResult{OK: true, Layer: health.LayerL7, Code: "L7OK"}
|
||||||
} else {
|
} 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()
|
c.mu.Lock()
|
||||||
w, exists := c.workers[key]
|
w, exists := c.workers[name]
|
||||||
if !exists {
|
if !exists {
|
||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
return
|
return
|
||||||
@@ -326,26 +361,38 @@ func (c *Checker) runProbe(ctx context.Context, key backendKey, pos, total int)
|
|||||||
t := w.backend.Transitions[0]
|
t := w.backend.Transitions[0]
|
||||||
addr := w.backend.Address
|
addr := w.backend.Address
|
||||||
slog.Info("backend-transition",
|
slog.Info("backend-transition",
|
||||||
"vip", key.VIPName,
|
"backend", name,
|
||||||
"backend", key.Backend,
|
|
||||||
"from", t.From.String(),
|
"from", t.From.String(),
|
||||||
"to", t.To.String(),
|
"to", t.To.String(),
|
||||||
"code", result.Code,
|
"code", result.Code,
|
||||||
"detail", result.Detail,
|
"detail", result.Detail,
|
||||||
)
|
)
|
||||||
c.emit(Event{VIPName: key.VIPName, Backend: addr, Transition: t})
|
c.emitForBackend(name, addr, t)
|
||||||
}
|
}
|
||||||
c.mu.Unlock()
|
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).
|
// emit sends an event to the internal fan-out channel (non-blocking).
|
||||||
// Must be called with c.mu held.
|
// Must be called with c.mu held.
|
||||||
func (c *Checker) emit(e Event) {
|
func (c *Checker) emit(e Event) {
|
||||||
select {
|
select {
|
||||||
case c.eventCh <- e:
|
case c.eventCh <- e:
|
||||||
default:
|
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
|
a.InsecureSkipVerify == b.InsecureSkipVerify
|
||||||
}
|
}
|
||||||
|
|
||||||
// staggerDelay computes the initial probe delay for a backend at position pos
|
// activeBackendNames returns a sorted, deduplicated list of backend names that
|
||||||
// out of total backends: delay = interval * pos / total.
|
// 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 {
|
func staggerDelay(interval time.Duration, pos, total int) time.Duration {
|
||||||
if total <= 1 {
|
if total <= 1 {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
return time.Duration(int64(interval) * int64(pos) / int64(total))
|
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
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -10,23 +10,32 @@ import (
|
|||||||
"git.ipng.ch/ipng/vpp-maglev/internal/health"
|
"git.ipng.ch/ipng/vpp-maglev/internal/health"
|
||||||
)
|
)
|
||||||
|
|
||||||
func makeTestConfig(interval time.Duration, fall, rise int) *config.Frontend {
|
func makeTestConfig(interval time.Duration, fall, rise int) *config.Config {
|
||||||
return &config.Frontend{
|
return &config.Config{
|
||||||
HealthCheckNetns: "test",
|
HealthChecker: config.HealthCheckerConfig{TransitionHistory: 5},
|
||||||
HealthChecker: config.HealthCheckerConfig{TransitionHistory: 5},
|
HealthChecks: map[string]config.HealthCheck{
|
||||||
VIPs: map[string]config.VIP{
|
"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": {
|
"web": {
|
||||||
Address: net.ParseIP("192.0.2.1"),
|
Address: net.ParseIP("192.0.2.1"),
|
||||||
Protocol: "tcp",
|
Protocol: "tcp",
|
||||||
Port: 80,
|
Port: 80,
|
||||||
Backends: []net.IP{net.ParseIP("10.0.0.2")},
|
Backends: []string{"be0"},
|
||||||
HealthCheck: config.HealthCheck{
|
|
||||||
Type: "icmp",
|
|
||||||
Interval: interval,
|
|
||||||
Timeout: time.Second,
|
|
||||||
Fall: fall,
|
|
||||||
Rise: rise,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -62,20 +71,16 @@ func TestHealthCheckEqual(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestStateMachineViaBackend(t *testing.T) {
|
func TestStateMachineViaBackend(t *testing.T) {
|
||||||
// Directly test Backend state transitions (rise=2, fall=3) without goroutines.
|
b := health.New("be0", net.ParseIP("10.0.0.2"), 2, 3)
|
||||||
b := health.New("web", net.ParseIP("10.0.0.2"), 2, 3)
|
|
||||||
pass := health.ProbeResult{OK: true, Layer: health.LayerL7, Code: "L7OK"}
|
pass := health.ProbeResult{OK: true, Layer: health.LayerL7, Code: "L7OK"}
|
||||||
fail := health.ProbeResult{OK: false, Layer: health.LayerL4, Code: "L4CON"}
|
fail := health.ProbeResult{OK: false, Layer: health.LayerL4, Code: "L4CON"}
|
||||||
|
|
||||||
// Unknown → Down on first fail.
|
|
||||||
if !b.Record(fail, 5) {
|
if !b.Record(fail, 5) {
|
||||||
t.Error("first fail from Unknown should transition to Down")
|
t.Error("first fail from Unknown should transition to Down")
|
||||||
}
|
}
|
||||||
if b.State != health.StateDown {
|
if b.State != health.StateDown {
|
||||||
t.Errorf("expected down, got %s", b.State)
|
t.Errorf("expected down, got %s", b.State)
|
||||||
}
|
}
|
||||||
|
|
||||||
// rise=2 passes → Up.
|
|
||||||
if b.Record(pass, 5) {
|
if b.Record(pass, 5) {
|
||||||
t.Error("should not transition after 1 pass (rise=2)")
|
t.Error("should not transition after 1 pass (rise=2)")
|
||||||
}
|
}
|
||||||
@@ -105,21 +110,19 @@ func TestReloadAddsBackend(t *testing.T) {
|
|||||||
c := New(cfg)
|
c := New(cfg)
|
||||||
|
|
||||||
newCfg := makeTestConfig(10*time.Millisecond, 3, 2)
|
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"),
|
Address: net.ParseIP("192.0.2.2"),
|
||||||
Protocol: "tcp",
|
Protocol: "tcp",
|
||||||
Port: 443,
|
Port: 443,
|
||||||
Backends: []net.IP{net.ParseIP("10.0.0.3")},
|
Backends: []string{"be1"},
|
||||||
HealthCheck: config.HealthCheck{
|
|
||||||
Type: "icmp",
|
|
||||||
Interval: 10 * time.Millisecond,
|
|
||||||
Timeout: time.Second,
|
|
||||||
Fall: 3,
|
|
||||||
Rise: 2,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cancelled context: no probe goroutines actually run.
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
cancel()
|
cancel()
|
||||||
|
|
||||||
@@ -128,7 +131,7 @@ func TestReloadAddsBackend(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
c.mu.RLock()
|
c.mu.RLock()
|
||||||
_, ok := c.workers[backendKey{VIPName: "web2", Backend: "10.0.0.3"}]
|
_, ok := c.workers["be1"]
|
||||||
c.mu.RUnlock()
|
c.mu.RUnlock()
|
||||||
if !ok {
|
if !ok {
|
||||||
t.Error("new backend not added after Reload")
|
t.Error("new backend not added after Reload")
|
||||||
@@ -144,22 +147,22 @@ func TestReloadRemovesBackend(t *testing.T) {
|
|||||||
|
|
||||||
// Seed a worker manually.
|
// Seed a worker manually.
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
key := backendKey{VIPName: "web", Backend: "10.0.0.2"}
|
|
||||||
wCtx, wCancel := context.WithCancel(context.Background())
|
wCtx, wCancel := context.WithCancel(context.Background())
|
||||||
c.workers[key] = &worker{
|
c.workers["be0"] = &worker{
|
||||||
backend: health.New("web", net.ParseIP("10.0.0.2"), 2, 3),
|
backend: health.New("be0", net.ParseIP("10.0.0.2"), 2, 3),
|
||||||
hc: cfg.VIPs["web"].HealthCheck,
|
hc: cfg.HealthChecks["icmp"],
|
||||||
vip: cfg.VIPs["web"],
|
entry: cfg.Backends["be0"],
|
||||||
cancel: wCancel,
|
cancel: wCancel,
|
||||||
}
|
}
|
||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
_ = wCtx
|
_ = wCtx
|
||||||
|
|
||||||
// New config with "web" VIP removed.
|
// Remove all frontends → be0 is no longer active.
|
||||||
newCfg := &config.Frontend{
|
newCfg := &config.Config{
|
||||||
HealthCheckNetns: cfg.HealthCheckNetns,
|
HealthChecker: cfg.HealthChecker,
|
||||||
HealthChecker: cfg.HealthChecker,
|
HealthChecks: cfg.HealthChecks,
|
||||||
VIPs: map[string]config.VIP{},
|
Backends: cfg.Backends,
|
||||||
|
Frontends: map[string]config.Frontend{},
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := c.Reload(ctx, newCfg); err != nil {
|
if err := c.Reload(ctx, newCfg); err != nil {
|
||||||
@@ -167,13 +170,30 @@ func TestReloadRemovesBackend(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
c.mu.RLock()
|
c.mu.RLock()
|
||||||
_, ok := c.workers[key]
|
_, ok := c.workers["be0"]
|
||||||
c.mu.RUnlock()
|
c.mu.RUnlock()
|
||||||
if ok {
|
if ok {
|
||||||
t.Error("removed backend still present after Reload")
|
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) {
|
func TestSubscribe(t *testing.T) {
|
||||||
cfg := makeTestConfig(10*time.Millisecond, 1, 1)
|
cfg := makeTestConfig(10*time.Millisecond, 1, 1)
|
||||||
c := New(cfg)
|
c := New(cfg)
|
||||||
@@ -186,8 +206,9 @@ func TestSubscribe(t *testing.T) {
|
|||||||
defer unsub()
|
defer unsub()
|
||||||
|
|
||||||
e := Event{
|
e := Event{
|
||||||
VIPName: "web",
|
FrontendName: "web",
|
||||||
Backend: net.ParseIP("10.0.0.2"),
|
BackendName: "be0",
|
||||||
|
Backend: net.ParseIP("10.0.0.2"),
|
||||||
Transition: health.Transition{
|
Transition: health.Transition{
|
||||||
From: health.StateUnknown,
|
From: health.StateUnknown,
|
||||||
To: health.StateUp,
|
To: health.StateUp,
|
||||||
@@ -199,8 +220,11 @@ func TestSubscribe(t *testing.T) {
|
|||||||
|
|
||||||
select {
|
select {
|
||||||
case got := <-ch:
|
case got := <-ch:
|
||||||
if got.VIPName != "web" {
|
if got.FrontendName != "web" {
|
||||||
t.Errorf("event VIPName: got %q, want %q", got.VIPName, "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 {
|
if got.Transition.To != health.StateUp {
|
||||||
t.Errorf("event To state: got %s, want up", got.Transition.To)
|
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) {
|
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)
|
c := New(cfg)
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
defer cancel()
|
defer cancel()
|
||||||
go c.fanOut(ctx)
|
go c.fanOut(ctx)
|
||||||
|
|
||||||
// Seed a worker.
|
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
key := backendKey{VIPName: "web", Backend: "10.0.0.2"}
|
|
||||||
_, wCancel := context.WithCancel(ctx)
|
_, wCancel := context.WithCancel(ctx)
|
||||||
c.workers[key] = &worker{
|
c.workers["be0"] = &worker{
|
||||||
backend: health.New("web", net.ParseIP("10.0.0.2"), 2, 3),
|
backend: health.New("be0", net.ParseIP("10.0.0.2"), 2, 3),
|
||||||
hc: cfg.VIPs["web"].HealthCheck,
|
hc: cfg.HealthChecks["icmp"],
|
||||||
vip: cfg.VIPs["web"],
|
entry: cfg.Backends["be0"],
|
||||||
cancel: wCancel,
|
cancel: wCancel,
|
||||||
}
|
}
|
||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
|
|
||||||
b, ok := c.PauseBackend("web", "10.0.0.2")
|
b, ok := c.PauseBackend("be0")
|
||||||
if !ok {
|
if !ok {
|
||||||
t.Fatal("PauseBackend: not found")
|
t.Fatal("PauseBackend: not found")
|
||||||
}
|
}
|
||||||
if b.State != health.StatePaused {
|
if b.Health.State != health.StatePaused {
|
||||||
t.Errorf("after pause: %s", b.State)
|
t.Errorf("after pause: %s", b.Health.State)
|
||||||
}
|
}
|
||||||
|
|
||||||
b, ok = c.ResumeBackend("web", "10.0.0.2")
|
b, ok = c.ResumeBackend("be0")
|
||||||
if !ok {
|
if !ok {
|
||||||
t.Fatal("ResumeBackend: not found")
|
t.Fatal("ResumeBackend: not found")
|
||||||
}
|
}
|
||||||
if b.State != health.StateUnknown {
|
if b.Health.State != health.StateUnknown {
|
||||||
t.Errorf("after resume: %s", b.State)
|
t.Errorf("after resume: %s", b.Health.State)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,35 +12,28 @@ import (
|
|||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Frontend is the parsed and validated representation of frontend.yaml.
|
// Config is the top-level parsed and validated configuration.
|
||||||
type Frontend struct {
|
type Config struct {
|
||||||
ProbeIPv4Src net.IP
|
HealthChecker HealthCheckerConfig
|
||||||
ProbeIPv6Src net.IP
|
HealthChecks map[string]HealthCheck
|
||||||
HealthCheckNetns string
|
Backends map[string]Backend
|
||||||
HealthChecker HealthCheckerConfig
|
Frontends map[string]Frontend
|
||||||
VIPs map[string]VIP
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// HealthCheckerConfig holds global health checker settings.
|
// HealthCheckerConfig holds global health checker settings.
|
||||||
type HealthCheckerConfig struct {
|
type HealthCheckerConfig struct {
|
||||||
TransitionHistory int
|
TransitionHistory int
|
||||||
|
Netns string // network namespace for probes; "" = current netns
|
||||||
}
|
}
|
||||||
|
|
||||||
// VIP is a single virtual IP entry.
|
// HealthCheck describes how to probe a backend.
|
||||||
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.
|
|
||||||
type HealthCheck struct {
|
type HealthCheck struct {
|
||||||
Type string
|
Type string
|
||||||
|
Port uint16 // destination port; required for tcp/http/https
|
||||||
HTTP *HTTPParams // non-nil for type http and https
|
HTTP *HTTPParams // non-nil for type http and https
|
||||||
TCP *TCPParams // non-nil for type tcp
|
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
|
Interval time.Duration
|
||||||
FastInterval time.Duration // optional; used while health counter is degraded
|
FastInterval time.Duration // optional; used while health counter is degraded
|
||||||
DownInterval time.Duration // optional; used while fully down
|
DownInterval time.Duration // optional; used while fully down
|
||||||
@@ -65,41 +58,49 @@ type TCPParams struct {
|
|||||||
SSL bool
|
SSL bool
|
||||||
ServerName string
|
ServerName string
|
||||||
InsecureSkipVerify bool
|
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 --------------------------------------------------------
|
// ---- raw YAML types --------------------------------------------------------
|
||||||
|
|
||||||
type rawConfig struct {
|
type rawConfig struct {
|
||||||
Maglev struct {
|
Maglev rawMaglev `yaml:"maglev"`
|
||||||
Frontend rawFrontend `yaml:"frontend"`
|
|
||||||
} `yaml:"maglev"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type rawFrontend struct {
|
type rawMaglev struct {
|
||||||
ProbeIPv4Src string `yaml:"probe-ipv4-src"`
|
HealthChecker rawHealthCheckerCfg `yaml:"healthchecker"`
|
||||||
ProbeIPv6Src string `yaml:"probe-ipv6-src"`
|
HealthChecks map[string]rawHealthCheck `yaml:"healthchecks"`
|
||||||
HealthCheckNetns string `yaml:"healthcheck-netns"`
|
Backends map[string]rawBackend `yaml:"backends"`
|
||||||
HealthChecker rawHealthCheckerCfg `yaml:"healthchecker"`
|
Frontends map[string]rawFrontend `yaml:"frontends"`
|
||||||
VIPs map[string]rawVIP `yaml:"vips"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type rawHealthCheckerCfg struct {
|
type rawHealthCheckerCfg struct {
|
||||||
TransitionHistory int `yaml:"transition-history"`
|
TransitionHistory int `yaml:"transition-history"`
|
||||||
}
|
Netns string `yaml:"netns"`
|
||||||
|
|
||||||
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"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type rawHealthCheck struct {
|
type rawHealthCheck struct {
|
||||||
Type string `yaml:"type"`
|
Type string `yaml:"type"`
|
||||||
|
Port uint16 `yaml:"port"`
|
||||||
Params rawParams `yaml:"params"`
|
Params rawParams `yaml:"params"`
|
||||||
|
ProbeIPv4Src string `yaml:"probe-ipv4-src"`
|
||||||
|
ProbeIPv6Src string `yaml:"probe-ipv6-src"`
|
||||||
Interval string `yaml:"interval"`
|
Interval string `yaml:"interval"`
|
||||||
FastInterval string `yaml:"fast-interval"`
|
FastInterval string `yaml:"fast-interval"`
|
||||||
DownInterval string `yaml:"down-interval"`
|
DownInterval string `yaml:"down-interval"`
|
||||||
@@ -120,10 +121,25 @@ type rawParams struct {
|
|||||||
SSL bool `yaml:"ssl"`
|
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 ------------------------------------------------------------------
|
||||||
|
|
||||||
// Load reads and validates the config file at path.
|
// 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)
|
data, err := os.ReadFile(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("read config %q: %w", path, err)
|
return nil, fmt.Errorf("read config %q: %w", path, err)
|
||||||
@@ -131,115 +147,82 @@ func Load(path string) (*Frontend, error) {
|
|||||||
return parse(data)
|
return parse(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
func parse(data []byte) (*Frontend, error) {
|
func parse(data []byte) (*Config, error) {
|
||||||
var raw rawConfig
|
var raw rawConfig
|
||||||
if err := yaml.Unmarshal(data, &raw); err != nil {
|
if err := yaml.Unmarshal(data, &raw); err != nil {
|
||||||
return nil, fmt.Errorf("parse yaml: %w", err)
|
return nil, fmt.Errorf("parse yaml: %w", err)
|
||||||
}
|
}
|
||||||
return convert(&raw.Maglev.Frontend)
|
return convert(&raw.Maglev)
|
||||||
}
|
}
|
||||||
|
|
||||||
func convert(r *rawFrontend) (*Frontend, error) {
|
func convert(r *rawMaglev) (*Config, error) {
|
||||||
f := &Frontend{}
|
cfg := &Config{}
|
||||||
var err error
|
|
||||||
|
|
||||||
if f.ProbeIPv4Src, err = parseOptionalIPFamily(r.ProbeIPv4Src, 4, "probe-ipv4-src"); err != nil {
|
// ---- healthchecker --------------------------------------------------------
|
||||||
return nil, err
|
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 {
|
if cfg.HealthChecker.TransitionHistory < 1 {
|
||||||
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 {
|
|
||||||
return nil, fmt.Errorf("healthchecker.transition-history must be >= 1")
|
return nil, fmt.Errorf("healthchecker.transition-history must be >= 1")
|
||||||
}
|
}
|
||||||
|
|
||||||
f.VIPs = make(map[string]VIP, len(r.VIPs))
|
// ---- healthchecks ---------------------------------------------------------
|
||||||
for name, rv := range r.VIPs {
|
cfg.HealthChecks = make(map[string]HealthCheck, len(r.HealthChecks))
|
||||||
vip, err := convertVIP(name, &rv)
|
for name, rh := range r.HealthChecks {
|
||||||
|
hc, err := convertHealthCheck(&rh)
|
||||||
if err != nil {
|
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
|
// ---- backends -------------------------------------------------------------
|
||||||
}
|
cfg.Backends = make(map[string]Backend, len(r.Backends))
|
||||||
|
for name, rb := range r.Backends {
|
||||||
func convertVIP(name string, r *rawVIP) (VIP, error) {
|
b, err := convertBackend(name, &rb, cfg.HealthChecks)
|
||||||
v := VIP{
|
if err != nil {
|
||||||
Description: r.Description,
|
return nil, fmt.Errorf("backend %q: %w", name, err)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
fam := ipFamily(ip)
|
cfg.Backends[name] = b
|
||||||
if i == 0 {
|
}
|
||||||
firstFamily = fam
|
|
||||||
} else if fam != firstFamily {
|
// ---- frontends ------------------------------------------------------------
|
||||||
return VIP{}, fmt.Errorf("backend[%d] %q has different address family than backend[0]", i, bs)
|
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)
|
return cfg, nil
|
||||||
if err != nil {
|
|
||||||
return VIP{}, fmt.Errorf("healthcheck: %w", err)
|
|
||||||
}
|
|
||||||
v.HealthCheck = hc
|
|
||||||
|
|
||||||
return v, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func convertHealthCheck(r *rawHealthCheck) (HealthCheck, error) {
|
func convertHealthCheck(r *rawHealthCheck) (HealthCheck, error) {
|
||||||
h := HealthCheck{
|
h := HealthCheck{Type: r.Type, Port: r.Port}
|
||||||
Type: r.Type,
|
|
||||||
}
|
|
||||||
|
|
||||||
switch r.Type {
|
switch r.Type {
|
||||||
case "icmp":
|
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":
|
case "tcp":
|
||||||
|
if r.Port == 0 {
|
||||||
|
return HealthCheck{}, fmt.Errorf("type tcp requires port")
|
||||||
|
}
|
||||||
h.TCP = &TCPParams{
|
h.TCP = &TCPParams{
|
||||||
SSL: r.Params.SSL,
|
SSL: r.Params.SSL,
|
||||||
ServerName: r.Params.ServerName,
|
ServerName: r.Params.ServerName,
|
||||||
InsecureSkipVerify: r.Params.InsecureSkipVerify,
|
InsecureSkipVerify: r.Params.InsecureSkipVerify,
|
||||||
}
|
}
|
||||||
case "http", "https":
|
case "http", "https":
|
||||||
|
if r.Port == 0 {
|
||||||
|
return HealthCheck{}, fmt.Errorf("type %s requires port", r.Type)
|
||||||
|
}
|
||||||
if r.Params.Path == "" {
|
if r.Params.Path == "" {
|
||||||
return HealthCheck{}, fmt.Errorf("type http requires params.path")
|
return HealthCheck{}, fmt.Errorf("type http requires params.path")
|
||||||
}
|
}
|
||||||
@@ -272,25 +255,33 @@ func convertHealthCheck(r *rawHealthCheck) (HealthCheck, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var err 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 == "" {
|
if r.Interval == "" {
|
||||||
return HealthCheck{}, fmt.Errorf("interval is required")
|
return HealthCheck{}, fmt.Errorf("interval is required")
|
||||||
}
|
}
|
||||||
if h.Interval, err = time.ParseDuration(r.Interval); err != nil || h.Interval <= 0 {
|
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)
|
return HealthCheck{}, fmt.Errorf("interval %q must be a positive duration", r.Interval)
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.FastInterval != "" {
|
if r.FastInterval != "" {
|
||||||
if h.FastInterval, err = time.ParseDuration(r.FastInterval); err != nil || h.FastInterval <= 0 {
|
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)
|
return HealthCheck{}, fmt.Errorf("fast-interval %q must be a positive duration", r.FastInterval)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.DownInterval != "" {
|
if r.DownInterval != "" {
|
||||||
if h.DownInterval, err = time.ParseDuration(r.DownInterval); err != nil || h.DownInterval <= 0 {
|
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)
|
return HealthCheck{}, fmt.Errorf("down-interval %q must be a positive duration", r.DownInterval)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.Timeout == "" {
|
if r.Timeout == "" {
|
||||||
return HealthCheck{}, fmt.Errorf("timeout is required")
|
return HealthCheck{}, fmt.Errorf("timeout is required")
|
||||||
}
|
}
|
||||||
@@ -305,7 +296,6 @@ func convertHealthCheck(r *rawHealthCheck) (HealthCheck, error) {
|
|||||||
if h.Fall < 1 {
|
if h.Fall < 1 {
|
||||||
return HealthCheck{}, fmt.Errorf("fall must be >= 1")
|
return HealthCheck{}, fmt.Errorf("fall must be >= 1")
|
||||||
}
|
}
|
||||||
|
|
||||||
h.Rise = r.Rise
|
h.Rise = r.Rise
|
||||||
if h.Rise == 0 {
|
if h.Rise == 0 {
|
||||||
h.Rise = 2
|
h.Rise = 2
|
||||||
@@ -317,10 +307,80 @@ func convertHealthCheck(r *rawHealthCheck) (HealthCheck, error) {
|
|||||||
return h, nil
|
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 ---------------------------------------------------------------
|
// ---- 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) {
|
func parseOptionalIPFamily(s string, family int, field string) (net.IP, error) {
|
||||||
if s == "" {
|
if s == "" {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
@@ -335,7 +395,6 @@ func parseOptionalIPFamily(s string, family int, field string) (net.IP, error) {
|
|||||||
return ip, nil
|
return ip, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ipFamily returns 4 for IPv4, 6 for IPv6.
|
|
||||||
func ipFamily(ip net.IP) int {
|
func ipFamily(ip net.IP) int {
|
||||||
if ip.To4() != nil {
|
if ip.To4() != nil {
|
||||||
return 4
|
return 4
|
||||||
@@ -343,8 +402,6 @@ func ipFamily(ip net.IP) int {
|
|||||||
return 6
|
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) {
|
func parseCodeRange(s string, defaultCode int) (min, max int, err error) {
|
||||||
if s == "" {
|
if s == "" {
|
||||||
return defaultCode, defaultCode, nil
|
return defaultCode, defaultCode, nil
|
||||||
@@ -366,3 +423,17 @@ func parseCodeRange(s string, defaultCode int) (min, max int, err error) {
|
|||||||
}
|
}
|
||||||
return min, min, nil
|
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
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,146 +7,217 @@ import (
|
|||||||
|
|
||||||
const validConfig = `
|
const validConfig = `
|
||||||
maglev:
|
maglev:
|
||||||
frontend:
|
healthchecker:
|
||||||
probe-ipv4-src: 10.0.0.1
|
transition-history: 5
|
||||||
probe-ipv6-src: 2001:db8:1::1
|
netns: dataplane
|
||||||
healthcheck-netns: dataplane
|
healthchecks:
|
||||||
healthchecker:
|
http-check:
|
||||||
transition-history: 5
|
type: http
|
||||||
vips:
|
port: 80
|
||||||
web4:
|
probe-ipv4-src: 10.0.0.1
|
||||||
description: "IPv4 VIP"
|
params:
|
||||||
address: 192.0.2.1
|
path: /healthz
|
||||||
protocol: tcp
|
host: example.com
|
||||||
port: 80
|
response-code: "200"
|
||||||
backends: [2001:db8:2::1, 2001:db8:2::2]
|
interval: 2s
|
||||||
healthcheck:
|
timeout: 3s
|
||||||
type: http
|
rise: 2
|
||||||
params:
|
fall: 3
|
||||||
path: /healthz
|
icmp-check:
|
||||||
host: example.com
|
type: icmp
|
||||||
response-code: "200"
|
probe-ipv6-src: 2001:db8:1::1
|
||||||
interval: 2s
|
interval: 1s
|
||||||
timeout: 3s
|
timeout: 3s
|
||||||
rise: 2
|
fall: 5
|
||||||
fall: 3
|
backends:
|
||||||
web6:
|
be-v4:
|
||||||
description: "IPv6 VIP"
|
address: 192.0.2.10
|
||||||
address: 2001:db8::1
|
healthcheck: http-check
|
||||||
protocol: tcp
|
be-v6a:
|
||||||
port: 443
|
address: 2001:db8:2::1
|
||||||
backends: [2001:db8:2::1, 2001:db8:2::2]
|
healthcheck: icmp-check
|
||||||
healthcheck:
|
be-v6b:
|
||||||
type: icmp
|
address: 2001:db8:2::2
|
||||||
interval: 1s
|
healthcheck: icmp-check
|
||||||
timeout: 3s
|
weight: 50
|
||||||
fall: 5
|
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) {
|
func TestValidConfig(t *testing.T) {
|
||||||
f, err := parse([]byte(validConfig))
|
cfg, err := parse([]byte(validConfig))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error: %v", err)
|
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" {
|
if cfg.HealthChecker.TransitionHistory != 5 {
|
||||||
t.Errorf("probe-ipv6-src: got %s, want 2001:db8:1::1", f.ProbeIPv6Src)
|
t.Errorf("transition-history: got %d, want 5", cfg.HealthChecker.TransitionHistory)
|
||||||
}
|
}
|
||||||
if f.HealthCheckNetns != "dataplane" {
|
if len(cfg.Frontends) != 2 {
|
||||||
t.Errorf("healthcheck-netns: got %q, want %q", f.HealthCheckNetns, "dataplane")
|
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 {
|
if hc.Fall != 3 || hc.Rise != 2 {
|
||||||
t.Fatalf("vips: got %d, want 2", len(f.VIPs))
|
t.Errorf("http-check fall/rise: got %d/%d, want 3/2", hc.Fall, hc.Rise)
|
||||||
}
|
}
|
||||||
web4 := f.VIPs["web4"]
|
if hc.ProbeIPv4Src.String() != "10.0.0.1" {
|
||||||
if web4.HealthCheck.Type != "http" {
|
t.Errorf("http-check probe-ipv4-src: got %s, want 10.0.0.1", hc.ProbeIPv4Src)
|
||||||
t.Errorf("web4 healthcheck type: got %q, want http", web4.HealthCheck.Type)
|
|
||||||
}
|
}
|
||||||
if web4.HealthCheck.Fall != 3 {
|
if hc.HTTP == nil {
|
||||||
t.Errorf("web4 fall: got %d, want 3", web4.HealthCheck.Fall)
|
t.Fatal("http-check HTTP params should not be nil")
|
||||||
}
|
}
|
||||||
if web4.HealthCheck.Rise != 2 {
|
if hc.HTTP.Path != "/healthz" {
|
||||||
t.Errorf("web4 rise: got %d, want 2", web4.HealthCheck.Rise)
|
t.Errorf("http-check path: got %q, want /healthz", hc.HTTP.Path)
|
||||||
}
|
}
|
||||||
if web4.HealthCheck.HTTP == nil {
|
if hc.HTTP.Host != "example.com" {
|
||||||
t.Fatal("web4 HTTP params should not be nil")
|
t.Errorf("http-check host: got %q, want example.com", hc.HTTP.Host)
|
||||||
}
|
}
|
||||||
if web4.HealthCheck.HTTP.Path != "/healthz" {
|
if hc.HTTP.ResponseCodeMin != 200 || hc.HTTP.ResponseCodeMax != 200 {
|
||||||
t.Errorf("web4 params.path: got %q, want /healthz", web4.HealthCheck.HTTP.Path)
|
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 {
|
if icmp.ProbeIPv6Src.String() != "2001:db8:1::1" {
|
||||||
t.Errorf("web4 response-code: got %d-%d, want 200-200",
|
t.Errorf("icmp-check probe-ipv6-src: got %s, want 2001:db8:1::1", icmp.ProbeIPv6Src)
|
||||||
web4.HealthCheck.HTTP.ResponseCodeMin, web4.HealthCheck.HTTP.ResponseCodeMax)
|
|
||||||
}
|
}
|
||||||
web6 := f.VIPs["web6"]
|
|
||||||
if web6.HealthCheck.Fall != 5 {
|
// Backend defaults and explicit fields.
|
||||||
t.Errorf("web6 fall: got %d, want 5", web6.HealthCheck.Fall)
|
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) {
|
func TestDefaults(t *testing.T) {
|
||||||
cfg := `
|
raw := `
|
||||||
maglev:
|
maglev:
|
||||||
frontend:
|
healthchecks:
|
||||||
vips:
|
icmp:
|
||||||
v:
|
type: icmp
|
||||||
address: 192.0.2.1
|
interval: 1s
|
||||||
backends: [10.0.0.2]
|
timeout: 2s
|
||||||
healthcheck:
|
backends:
|
||||||
type: icmp
|
be:
|
||||||
interval: 1s
|
address: 10.0.0.2
|
||||||
timeout: 2s
|
healthcheck: icmp
|
||||||
|
frontends:
|
||||||
|
v:
|
||||||
|
address: 192.0.2.1
|
||||||
|
backends: [be]
|
||||||
`
|
`
|
||||||
f, err := parse([]byte(cfg))
|
cfg, err := parse([]byte(raw))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error: %v", err)
|
t.Fatalf("unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
if f.HealthCheckNetns != "" {
|
if cfg.HealthChecker.Netns != "" {
|
||||||
t.Errorf("default healthcheck-netns: got %q, want empty (default netns)", f.HealthCheckNetns)
|
t.Errorf("default netns: got %q, want empty", cfg.HealthChecker.Netns)
|
||||||
}
|
}
|
||||||
if f.HealthChecker.TransitionHistory != 5 {
|
if cfg.HealthChecker.TransitionHistory != 5 {
|
||||||
t.Errorf("default transition-history: got %d, want 5", f.HealthChecker.TransitionHistory)
|
t.Errorf("default transition-history: got %d, want 5", cfg.HealthChecker.TransitionHistory)
|
||||||
}
|
}
|
||||||
hc := f.VIPs["v"].HealthCheck
|
hc := cfg.HealthChecks["icmp"]
|
||||||
if hc.Rise != 2 {
|
if hc.Rise != 2 || hc.Fall != 3 {
|
||||||
t.Errorf("default rise: got %d, want 2", hc.Rise)
|
t.Errorf("defaults rise/fall: got %d/%d, want 2/3", hc.Rise, hc.Fall)
|
||||||
}
|
}
|
||||||
if hc.Fall != 3 {
|
be := cfg.Backends["be"]
|
||||||
t.Errorf("default fall: got %d, want 3", hc.Fall)
|
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) {
|
func TestOptionalIntervals(t *testing.T) {
|
||||||
cfg := `
|
raw := `
|
||||||
maglev:
|
maglev:
|
||||||
frontend:
|
healthchecks:
|
||||||
vips:
|
icmp:
|
||||||
v:
|
type: icmp
|
||||||
address: 192.0.2.1
|
interval: 2s
|
||||||
backends: [10.0.0.2]
|
fast-interval: 500ms
|
||||||
healthcheck:
|
down-interval: 30s
|
||||||
type: icmp
|
timeout: 1s
|
||||||
interval: 2s
|
backends:
|
||||||
fast-interval: 500ms
|
be:
|
||||||
down-interval: 30s
|
address: 10.0.0.2
|
||||||
timeout: 1s
|
healthcheck: icmp
|
||||||
|
frontends:
|
||||||
|
v:
|
||||||
|
address: 192.0.2.1
|
||||||
|
backends: [be]
|
||||||
`
|
`
|
||||||
f, err := parse([]byte(cfg))
|
cfg, err := parse([]byte(raw))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error: %v", err)
|
t.Fatalf("unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
hc := f.VIPs["v"].HealthCheck
|
hc := cfg.HealthChecks["icmp"]
|
||||||
if hc.Interval != 2*time.Second {
|
if hc.Interval != 2*time.Second {
|
||||||
t.Errorf("interval: got %v, want 2s", hc.Interval)
|
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) {
|
func TestValidationErrors(t *testing.T) {
|
||||||
// Minimal valid base to build error cases on top of.
|
base := func(hcExtra, beExtra, feExtra string) string {
|
||||||
base := func(override string) string {
|
|
||||||
return `
|
return `
|
||||||
maglev:
|
maglev:
|
||||||
frontend:
|
healthchecks:
|
||||||
vips:
|
c:
|
||||||
v:
|
type: icmp
|
||||||
address: 192.0.2.1
|
interval: 1s
|
||||||
backends: [10.0.0.2]
|
timeout: 2s
|
||||||
healthcheck:
|
` + hcExtra + `
|
||||||
type: icmp
|
backends:
|
||||||
interval: 1s
|
be:
|
||||||
timeout: 2s
|
address: 10.0.0.2
|
||||||
` + override
|
healthcheck: c
|
||||||
|
` + beExtra + `
|
||||||
|
frontends:
|
||||||
|
v:
|
||||||
|
address: 192.0.2.1
|
||||||
|
backends: [be]
|
||||||
|
` + feExtra
|
||||||
}
|
}
|
||||||
_ = base
|
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
@@ -206,58 +257,50 @@ maglev:
|
|||||||
errSub string
|
errSub string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "wrong family probe-ipv4-src",
|
name: "wrong family probe-ipv4-src",
|
||||||
yaml: `
|
yaml: base(" probe-ipv4-src: 2001:db8::1\n", "", ""),
|
||||||
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
|
|
||||||
`,
|
|
||||||
errSub: "probe-ipv4-src",
|
errSub: "probe-ipv4-src",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "mixed backend families",
|
name: "mixed backend address families in frontend",
|
||||||
yaml: `
|
yaml: `
|
||||||
maglev:
|
maglev:
|
||||||
frontend:
|
healthchecks:
|
||||||
vips:
|
c:
|
||||||
v:
|
type: icmp
|
||||||
address: 192.0.2.1
|
interval: 1s
|
||||||
backends: [10.0.0.2, 2001:db8::1]
|
timeout: 2s
|
||||||
healthcheck:
|
backends:
|
||||||
type: icmp
|
v4: {address: 10.0.0.2, healthcheck: c}
|
||||||
interval: 1s
|
v6: {address: 2001:db8::1, healthcheck: c}
|
||||||
timeout: 2s
|
frontends:
|
||||||
|
v:
|
||||||
|
address: 192.0.2.1
|
||||||
|
backends: [v4, v6]
|
||||||
`,
|
`,
|
||||||
errSub: "address family",
|
errSub: "address family",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "port without protocol",
|
name: "port without protocol",
|
||||||
yaml: validPortWithoutProtocol,
|
yaml: base("", "", " port: 80\n"),
|
||||||
|
|
||||||
errSub: "port requires protocol",
|
errSub: "port requires protocol",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "protocol without port",
|
name: "protocol without port",
|
||||||
yaml: `
|
yaml: `
|
||||||
maglev:
|
maglev:
|
||||||
frontend:
|
healthchecks:
|
||||||
vips:
|
c:
|
||||||
v:
|
type: icmp
|
||||||
address: 192.0.2.1
|
interval: 1s
|
||||||
protocol: tcp
|
timeout: 2s
|
||||||
backends: [10.0.0.2]
|
backends:
|
||||||
healthcheck:
|
be: {address: 10.0.0.2, healthcheck: c}
|
||||||
type: icmp
|
frontends:
|
||||||
interval: 1s
|
v:
|
||||||
timeout: 2s
|
address: 192.0.2.1
|
||||||
|
protocol: tcp
|
||||||
|
backends: [be]
|
||||||
`,
|
`,
|
||||||
errSub: "requires port",
|
errSub: "requires port",
|
||||||
},
|
},
|
||||||
@@ -265,15 +308,17 @@ maglev:
|
|||||||
name: "invalid healthcheck type",
|
name: "invalid healthcheck type",
|
||||||
yaml: `
|
yaml: `
|
||||||
maglev:
|
maglev:
|
||||||
frontend:
|
healthchecks:
|
||||||
vips:
|
c:
|
||||||
v:
|
type: dns
|
||||||
address: 192.0.2.1
|
interval: 1s
|
||||||
backends: [10.0.0.2]
|
timeout: 2s
|
||||||
healthcheck:
|
backends:
|
||||||
type: dns
|
be: {address: 10.0.0.2, healthcheck: c}
|
||||||
interval: 1s
|
frontends:
|
||||||
timeout: 2s
|
v:
|
||||||
|
address: 192.0.2.1
|
||||||
|
backends: [be]
|
||||||
`,
|
`,
|
||||||
errSub: "type must be",
|
errSub: "type must be",
|
||||||
},
|
},
|
||||||
@@ -281,84 +326,104 @@ maglev:
|
|||||||
name: "http missing path",
|
name: "http missing path",
|
||||||
yaml: `
|
yaml: `
|
||||||
maglev:
|
maglev:
|
||||||
frontend:
|
healthchecks:
|
||||||
vips:
|
c:
|
||||||
v:
|
type: http
|
||||||
address: 192.0.2.1
|
port: 80
|
||||||
backends: [10.0.0.2]
|
interval: 1s
|
||||||
healthcheck:
|
timeout: 2s
|
||||||
type: http
|
backends:
|
||||||
interval: 1s
|
be: {address: 10.0.0.2, healthcheck: c}
|
||||||
timeout: 2s
|
frontends:
|
||||||
|
v:
|
||||||
|
address: 192.0.2.1
|
||||||
|
backends: [be]
|
||||||
`,
|
`,
|
||||||
errSub: "params.path",
|
errSub: "params.path",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "negative interval",
|
name: "negative interval",
|
||||||
yaml: `
|
yaml: base("", "", ""),
|
||||||
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
|
|
||||||
errSub: "",
|
errSub: "",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "empty backends",
|
name: "undefined healthcheck reference",
|
||||||
yaml: `
|
yaml: `
|
||||||
maglev:
|
maglev:
|
||||||
frontend:
|
healthchecks: {}
|
||||||
vips:
|
backends:
|
||||||
v:
|
be: {address: 10.0.0.2, healthcheck: missing}
|
||||||
address: 192.0.2.1
|
frontends:
|
||||||
backends: []
|
v:
|
||||||
healthcheck:
|
address: 192.0.2.1
|
||||||
type: icmp
|
backends: [be]
|
||||||
interval: 1s
|
|
||||||
timeout: 2s
|
|
||||||
`,
|
`,
|
||||||
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 {
|
func contains(s, sub string) bool {
|
||||||
return len(s) >= len(sub) && (s == sub || len(sub) == 0 ||
|
return len(s) >= len(sub) && (s == sub || len(sub) == 0 ||
|
||||||
func() bool {
|
func() bool {
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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",
|
|
||||||
}
|
|
||||||
1232
internal/grpcapi/maglev.pb.go
Normal file
1232
internal/grpcapi/maglev.pb.go
Normal file
File diff suppressed because it is too large
Load Diff
433
internal/grpcapi/maglev_grpc.pb.go
Normal file
433
internal/grpcapi/maglev_grpc.pb.go
Normal file
@@ -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",
|
||||||
|
}
|
||||||
@@ -2,7 +2,6 @@ package grpcapi
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
"google.golang.org/grpc/codes"
|
"google.golang.org/grpc/codes"
|
||||||
@@ -13,9 +12,9 @@ import (
|
|||||||
"git.ipng.ch/ipng/vpp-maglev/internal/health"
|
"git.ipng.ch/ipng/vpp-maglev/internal/health"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Server implements the HealthCheckerServer gRPC interface.
|
// Server implements the MaglevServer gRPC interface.
|
||||||
type Server struct {
|
type Server struct {
|
||||||
UnimplementedHealthCheckerServer
|
UnimplementedMaglevServer
|
||||||
checker *checker.Checker
|
checker *checker.Checker
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -24,85 +23,88 @@ func NewServer(c *checker.Checker) *Server {
|
|||||||
return &Server{checker: c}
|
return &Server{checker: c}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListVIPs returns the names of all configured VIPs.
|
// ListFrontends returns the names of all configured frontends.
|
||||||
func (s *Server) ListVIPs(_ context.Context, _ *ListVIPsRequest) (*ListVIPsResponse, error) {
|
func (s *Server) ListFrontends(_ context.Context, _ *ListFrontendsRequest) (*ListFrontendsResponse, error) {
|
||||||
return &ListVIPsResponse{VipNames: s.checker.ListVIPs()}, nil
|
return &ListFrontendsResponse{FrontendNames: s.checker.ListFrontends()}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetVIP returns configuration details for a single VIP.
|
// GetFrontend returns configuration details for a single frontend.
|
||||||
func (s *Server) GetVIP(_ context.Context, req *GetVIPRequest) (*VIPInfo, error) {
|
func (s *Server) GetFrontend(_ context.Context, req *GetFrontendRequest) (*FrontendInfo, error) {
|
||||||
vip, ok := s.checker.GetVIP(req.VipName)
|
fe, ok := s.checker.GetFrontend(req.Name)
|
||||||
if !ok {
|
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.
|
// ListBackends returns the names of all active backends.
|
||||||
func (s *Server) ListBackends(_ context.Context, req *ListBackendsRequest) (*ListBackendsResponse, error) {
|
func (s *Server) ListBackends(_ context.Context, _ *ListBackendsRequest) (*ListBackendsResponse, error) {
|
||||||
if _, ok := s.checker.GetVIP(req.VipName); !ok {
|
return &ListBackendsResponse{BackendNames: s.checker.ListBackends()}, nil
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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) {
|
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 {
|
if !ok {
|
||||||
return nil, status.Errorf(codes.NotFound, "backend %q in vip %q not found",
|
return nil, status.Errorf(codes.NotFound, "backend %q not found", req.Name)
|
||||||
req.BackendAddress, req.VipName)
|
|
||||||
}
|
}
|
||||||
return backendToProto(b), nil
|
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) {
|
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 {
|
if !ok {
|
||||||
return nil, status.Errorf(codes.NotFound, "backend %q in vip %q not found",
|
return nil, status.Errorf(codes.NotFound, "backend %q not found", req.Name)
|
||||||
req.BackendAddress, req.VipName)
|
|
||||||
}
|
}
|
||||||
return backendToProto(b), nil
|
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) {
|
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 {
|
if !ok {
|
||||||
return nil, status.Errorf(codes.NotFound, "backend %q in vip %q not found",
|
return nil, status.Errorf(codes.NotFound, "backend %q not found", req.Name)
|
||||||
req.BackendAddress, req.VipName)
|
|
||||||
}
|
}
|
||||||
return backendToProto(b), nil
|
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.
|
// 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.
|
// Send current state of all backends as synthetic events.
|
||||||
for _, vipName := range s.checker.ListVIPs() {
|
for _, name := range s.checker.ListBackends() {
|
||||||
for _, b := range s.checker.ListBackends(vipName) {
|
snap, ok := s.checker.GetBackend(name)
|
||||||
ev := &TransitionEvent{
|
if !ok {
|
||||||
VipName: vipName,
|
continue
|
||||||
BackendAddress: b.Address.String(),
|
}
|
||||||
Transition: &TransitionRecord{
|
ev := &BackendEvent{
|
||||||
From: b.State.String(),
|
BackendName: name,
|
||||||
To: b.State.String(),
|
Transition: &TransitionRecord{
|
||||||
AtUnixNs: 0,
|
From: snap.Health.State.String(),
|
||||||
},
|
To: snap.Health.State.String(),
|
||||||
}
|
AtUnixNs: 0,
|
||||||
if err := stream.Send(ev); err != nil {
|
},
|
||||||
return err
|
}
|
||||||
}
|
if err := stream.Send(ev); err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Subscribe to live transitions.
|
|
||||||
ch, unsub := s.checker.Subscribe()
|
ch, unsub := s.checker.Subscribe()
|
||||||
defer unsub()
|
defer unsub()
|
||||||
|
|
||||||
@@ -114,10 +116,9 @@ func (s *Server) WatchTransitions(_ *WatchRequest, stream HealthChecker_WatchTra
|
|||||||
if !ok {
|
if !ok {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
ev := &TransitionEvent{
|
ev := &BackendEvent{
|
||||||
VipName: e.VIPName,
|
BackendName: e.BackendName,
|
||||||
BackendAddress: e.Backend.String(),
|
Transition: transitionToProto(e.Transition),
|
||||||
Transition: transitionToProto(e.Transition),
|
|
||||||
}
|
}
|
||||||
if err := stream.Send(ev); err != nil {
|
if err := stream.Send(ev); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -128,28 +129,71 @@ func (s *Server) WatchTransitions(_ *WatchRequest, stream HealthChecker_WatchTra
|
|||||||
|
|
||||||
// ---- conversion helpers ----------------------------------------------------
|
// ---- conversion helpers ----------------------------------------------------
|
||||||
|
|
||||||
func vipToProto(name string, v config.VIP) *VIPInfo {
|
func frontendToProto(name string, fe config.Frontend) *FrontendInfo {
|
||||||
info := &VIPInfo{
|
return &FrontendInfo{
|
||||||
Name: name,
|
Name: name,
|
||||||
Address: v.Address.String(),
|
Address: fe.Address.String(),
|
||||||
Protocol: v.Protocol,
|
Protocol: fe.Protocol,
|
||||||
Port: uint32(v.Port),
|
Port: uint32(fe.Port),
|
||||||
Description: v.Description,
|
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
|
return info
|
||||||
}
|
}
|
||||||
|
|
||||||
func backendToProto(b *health.Backend) *BackendInfo {
|
func healthCheckToProto(name string, hc config.HealthCheck) *HealthCheckInfo {
|
||||||
info := &BackendInfo{
|
info := &HealthCheckInfo{
|
||||||
VipName: b.VIPName,
|
Name: name,
|
||||||
Address: b.Address.String(),
|
Type: hc.Type,
|
||||||
State: b.State.String(),
|
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 {
|
if hc.ProbeIPv4Src != nil {
|
||||||
info.Transitions = append(info.Transitions, transitionToProto(t))
|
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
|
return info
|
||||||
}
|
}
|
||||||
@@ -164,4 +208,3 @@ func transitionToProto(t health.Transition) *TransitionRecord {
|
|||||||
|
|
||||||
// Ensure net.IP is imported (used via b.Address.String()).
|
// Ensure net.IP is imported (used via b.Address.String()).
|
||||||
var _ = net.IP{}
|
var _ = net.IP{}
|
||||||
var _ = fmt.Sprintf
|
|
||||||
|
|||||||
@@ -15,22 +15,31 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func makeTestChecker(ctx context.Context) *checker.Checker {
|
func makeTestChecker(ctx context.Context) *checker.Checker {
|
||||||
cfg := &config.Frontend{
|
cfg := &config.Config{
|
||||||
HealthCheckNetns: "test",
|
HealthChecker: config.HealthCheckerConfig{TransitionHistory: 5},
|
||||||
HealthChecker: config.HealthCheckerConfig{TransitionHistory: 5},
|
HealthChecks: map[string]config.HealthCheck{
|
||||||
VIPs: map[string]config.VIP{
|
"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": {
|
"web": {
|
||||||
Address: net.ParseIP("192.0.2.1"),
|
Address: net.ParseIP("192.0.2.1"),
|
||||||
Protocol: "tcp",
|
Protocol: "tcp",
|
||||||
Port: 80,
|
Port: 80,
|
||||||
Backends: []net.IP{net.ParseIP("10.0.0.2")},
|
Backends: []string{"be0"},
|
||||||
HealthCheck: config.HealthCheck{
|
|
||||||
Type: "icmp",
|
|
||||||
Interval: time.Hour, // long interval: probes won't fire during tests
|
|
||||||
Timeout: time.Second,
|
|
||||||
Fall: 3,
|
|
||||||
Rise: 2,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -41,14 +50,14 @@ func makeTestChecker(ctx context.Context) *checker.Checker {
|
|||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
func startTestServer(t *testing.T, c *checker.Checker) (HealthCheckerClient, func()) {
|
func startTestServer(t *testing.T, c *checker.Checker) (MaglevClient, func()) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
lis, err := net.Listen("tcp", "127.0.0.1:0")
|
lis, err := net.Listen("tcp", "127.0.0.1:0")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("listen: %v", err)
|
t.Fatalf("listen: %v", err)
|
||||||
}
|
}
|
||||||
srv := grpc.NewServer()
|
srv := grpc.NewServer()
|
||||||
RegisterHealthCheckerServer(srv, NewServer(c))
|
RegisterMaglevServer(srv, NewServer(c))
|
||||||
go srv.Serve(lis) //nolint:errcheck
|
go srv.Serve(lis) //nolint:errcheck
|
||||||
|
|
||||||
conn, err := grpc.NewClient(lis.Addr().String(),
|
conn, err := grpc.NewClient(lis.Addr().String(),
|
||||||
@@ -56,13 +65,13 @@ func startTestServer(t *testing.T, c *checker.Checker) (HealthCheckerClient, fun
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("dial: %v", err)
|
t.Fatalf("dial: %v", err)
|
||||||
}
|
}
|
||||||
return NewHealthCheckerClient(conn), func() {
|
return NewMaglevClient(conn), func() {
|
||||||
conn.Close()
|
conn.Close()
|
||||||
srv.Stop()
|
srv.Stop()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestListVIPs(t *testing.T) {
|
func TestListFrontends(t *testing.T) {
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
@@ -70,16 +79,16 @@ func TestListVIPs(t *testing.T) {
|
|||||||
client, cleanup := startTestServer(t, c)
|
client, cleanup := startTestServer(t, c)
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
resp, err := client.ListVIPs(ctx, &ListVIPsRequest{})
|
resp, err := client.ListFrontends(ctx, &ListFrontendsRequest{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("ListVIPs: %v", err)
|
t.Fatalf("ListFrontends: %v", err)
|
||||||
}
|
}
|
||||||
if len(resp.VipNames) != 1 || resp.VipNames[0] != "web" {
|
if len(resp.FrontendNames) != 1 || resp.FrontendNames[0] != "web" {
|
||||||
t.Errorf("ListVIPs: got %v, want [web]", resp.VipNames)
|
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())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
@@ -87,19 +96,22 @@ func TestGetVIP(t *testing.T) {
|
|||||||
client, cleanup := startTestServer(t, c)
|
client, cleanup := startTestServer(t, c)
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
info, err := client.GetVIP(ctx, &GetVIPRequest{VipName: "web"})
|
info, err := client.GetFrontend(ctx, &GetFrontendRequest{Name:"web"})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("GetVIP: %v", err)
|
t.Fatalf("GetFrontend: %v", err)
|
||||||
}
|
}
|
||||||
if info.Address != "192.0.2.1" {
|
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 {
|
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())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
@@ -107,9 +119,26 @@ func TestGetVIPNotFound(t *testing.T) {
|
|||||||
client, cleanup := startTestServer(t, c)
|
client, cleanup := startTestServer(t, c)
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
_, err := client.GetVIP(ctx, &GetVIPRequest{VipName: "nope"})
|
_, err := client.GetFrontend(ctx, &GetFrontendRequest{Name:"nope"})
|
||||||
if err == nil {
|
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)
|
client, cleanup := startTestServer(t, c)
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
info, err := client.GetBackend(ctx, &GetBackendRequest{
|
info, err := client.GetBackend(ctx, &GetBackendRequest{Name:"be0"})
|
||||||
VipName: "web",
|
|
||||||
BackendAddress: "10.0.0.2",
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("GetBackend: %v", err)
|
t.Fatalf("GetBackend: %v", err)
|
||||||
}
|
}
|
||||||
if info.State != health.StateUnknown.String() {
|
if info.State != health.StateUnknown.String() {
|
||||||
t.Errorf("initial state: got %q, want unknown", info.State)
|
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) {
|
func TestPauseResumeBackend(t *testing.T) {
|
||||||
@@ -141,10 +190,7 @@ func TestPauseResumeBackend(t *testing.T) {
|
|||||||
client, cleanup := startTestServer(t, c)
|
client, cleanup := startTestServer(t, c)
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
info, err := client.PauseBackend(ctx, &PauseResumeRequest{
|
info, err := client.PauseBackend(ctx, &PauseResumeRequest{Name:"be0"})
|
||||||
VipName: "web",
|
|
||||||
BackendAddress: "10.0.0.2",
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("PauseBackend: %v", err)
|
t.Fatalf("PauseBackend: %v", err)
|
||||||
}
|
}
|
||||||
@@ -152,10 +198,7 @@ func TestPauseResumeBackend(t *testing.T) {
|
|||||||
t.Errorf("after pause: got %q, want paused", info.State)
|
t.Errorf("after pause: got %q, want paused", info.State)
|
||||||
}
|
}
|
||||||
|
|
||||||
info, err = client.ResumeBackend(ctx, &PauseResumeRequest{
|
info, err = client.ResumeBackend(ctx, &PauseResumeRequest{Name:"be0"})
|
||||||
VipName: "web",
|
|
||||||
BackendAddress: "10.0.0.2",
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("ResumeBackend: %v", err)
|
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())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
@@ -172,17 +215,68 @@ func TestWatchTransitions(t *testing.T) {
|
|||||||
client, cleanup := startTestServer(t, c)
|
client, cleanup := startTestServer(t, c)
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
stream, err := client.WatchTransitions(ctx, &WatchRequest{})
|
resp, err := client.ListHealthChecks(ctx, &ListHealthChecksRequest{})
|
||||||
if err != nil {
|
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()
|
ev, err := stream.Recv()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Recv: %v", err)
|
t.Fatalf("Recv: %v", err)
|
||||||
}
|
}
|
||||||
if ev.VipName != "web" || ev.BackendAddress != "10.0.0.2" {
|
if ev.BackendName != "be0" {
|
||||||
t.Errorf("initial event: vip=%q backend=%q", ev.VipName, ev.BackendAddress)
|
t.Errorf("initial event: backend=%q, want be0", ev.BackendName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -90,9 +90,9 @@ func (h *HealthCounter) RecordFail() bool {
|
|||||||
return !wasDown && !h.IsUp()
|
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 {
|
type Backend struct {
|
||||||
VIPName string
|
Name string
|
||||||
Address net.IP
|
Address net.IP
|
||||||
State State
|
State State
|
||||||
Counter HealthCounter
|
Counter HealthCounter
|
||||||
@@ -100,9 +100,9 @@ type Backend struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// New creates a Backend in StateUnknown.
|
// 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{
|
return &Backend{
|
||||||
VIPName: vipName,
|
Name: name,
|
||||||
Address: addr,
|
Address: addr,
|
||||||
State: StateUnknown,
|
State: StateUnknown,
|
||||||
Counter: HealthCounter{Rise: rise, Fall: fall},
|
Counter: HealthCounter{Rise: rise, Fall: fall},
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
120
proto/maglev.proto
Normal file
120
proto/maglev.proto
Normal file
@@ -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;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user