package grpcapi import ( "context" "net" "testing" "time" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" "git.ipng.ch/ipng/vpp-maglev/internal/checker" "git.ipng.ch/ipng/vpp-maglev/internal/config" "git.ipng.ch/ipng/vpp-maglev/internal/health" ) func makeTestChecker(ctx context.Context) *checker.Checker { cfg := &config.Frontend{ HealthCheckNetns: "test", HealthChecker: config.HealthCheckerConfig{TransitionHistory: 5}, VIPs: map[string]config.VIP{ "web": { Address: net.ParseIP("192.0.2.1"), Protocol: "tcp", Port: 80, Backends: []net.IP{net.ParseIP("10.0.0.2")}, HealthCheck: config.HealthCheck{ Type: "icmp", Interval: time.Hour, // long interval: probes won't fire during tests Timeout: time.Second, Fall: 3, Rise: 2, }, }, }, } c := checker.New(cfg) go c.Run(ctx) //nolint:errcheck // Allow the Run goroutine to initialize workers. time.Sleep(10 * time.Millisecond) return c } func startTestServer(t *testing.T, c *checker.Checker) (HealthCheckerClient, func()) { t.Helper() lis, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { t.Fatalf("listen: %v", err) } srv := grpc.NewServer() RegisterHealthCheckerServer(srv, NewServer(c)) go srv.Serve(lis) //nolint:errcheck conn, err := grpc.NewClient(lis.Addr().String(), grpc.WithTransportCredentials(insecure.NewCredentials())) if err != nil { t.Fatalf("dial: %v", err) } return NewHealthCheckerClient(conn), func() { conn.Close() srv.Stop() } } func TestListVIPs(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() c := makeTestChecker(ctx) client, cleanup := startTestServer(t, c) defer cleanup() resp, err := client.ListVIPs(ctx, &ListVIPsRequest{}) if err != nil { t.Fatalf("ListVIPs: %v", err) } if len(resp.VipNames) != 1 || resp.VipNames[0] != "web" { t.Errorf("ListVIPs: got %v, want [web]", resp.VipNames) } } func TestGetVIP(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() c := makeTestChecker(ctx) client, cleanup := startTestServer(t, c) defer cleanup() info, err := client.GetVIP(ctx, &GetVIPRequest{VipName: "web"}) if err != nil { t.Fatalf("GetVIP: %v", err) } if info.Address != "192.0.2.1" { t.Errorf("GetVIP address: got %q, want 192.0.2.1", info.Address) } if info.Port != 80 { t.Errorf("GetVIP port: got %d, want 80", info.Port) } } func TestGetVIPNotFound(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() c := makeTestChecker(ctx) client, cleanup := startTestServer(t, c) defer cleanup() _, err := client.GetVIP(ctx, &GetVIPRequest{VipName: "nope"}) if err == nil { t.Error("expected error for unknown VIP") } } func TestGetBackend(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() c := makeTestChecker(ctx) client, cleanup := startTestServer(t, c) defer cleanup() info, err := client.GetBackend(ctx, &GetBackendRequest{ VipName: "web", BackendAddress: "10.0.0.2", }) if err != nil { t.Fatalf("GetBackend: %v", err) } if info.State != health.StateUnknown.String() { t.Errorf("initial state: got %q, want unknown", info.State) } } func TestPauseResumeBackend(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() c := makeTestChecker(ctx) client, cleanup := startTestServer(t, c) defer cleanup() info, err := client.PauseBackend(ctx, &PauseResumeRequest{ VipName: "web", BackendAddress: "10.0.0.2", }) if err != nil { t.Fatalf("PauseBackend: %v", err) } if info.State != health.StatePaused.String() { t.Errorf("after pause: got %q, want paused", info.State) } info, err = client.ResumeBackend(ctx, &PauseResumeRequest{ VipName: "web", BackendAddress: "10.0.0.2", }) if err != nil { t.Fatalf("ResumeBackend: %v", err) } if info.State != health.StateUnknown.String() { t.Errorf("after resume: got %q, want unknown", info.State) } } func TestWatchTransitions(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() c := makeTestChecker(ctx) client, cleanup := startTestServer(t, c) defer cleanup() stream, err := client.WatchTransitions(ctx, &WatchRequest{}) if err != nil { t.Fatalf("WatchTransitions: %v", err) } // Should receive the current state for web:10.0.0.2 immediately. ev, err := stream.Recv() if err != nil { t.Fatalf("Recv: %v", err) } if ev.VipName != "web" || ev.BackendAddress != "10.0.0.2" { t.Errorf("initial event: vip=%q backend=%q", ev.VipName, ev.BackendAddress) } }