mirror of
https://github.com/netbirdio/netbird.git
synced 2026-04-16 15:26:40 +00:00
add CLI
This commit is contained in:
@@ -17,6 +17,56 @@ Load testing tool for the NetBird signal server.
|
||||
|
||||
## Usage
|
||||
|
||||
### Standalone Binary
|
||||
|
||||
Build and run the load test as a standalone binary:
|
||||
|
||||
```bash
|
||||
# Build the binary
|
||||
cd signal/loadtest/cmd/signal-loadtest
|
||||
go build -o signal-loadtest
|
||||
|
||||
# Single message exchange
|
||||
./signal-loadtest \
|
||||
-server http://localhost:10000 \
|
||||
-pairs-per-sec 10 \
|
||||
-total-pairs 100 \
|
||||
-message-size 100
|
||||
|
||||
# Continuous exchange for 30 seconds
|
||||
./signal-loadtest \
|
||||
-server http://localhost:10000 \
|
||||
-pairs-per-sec 10 \
|
||||
-total-pairs 20 \
|
||||
-message-size 200 \
|
||||
-exchange-duration 30s \
|
||||
-message-interval 200ms
|
||||
|
||||
# Long-running test (10 minutes)
|
||||
./signal-loadtest \
|
||||
-server http://localhost:10000 \
|
||||
-pairs-per-sec 20 \
|
||||
-total-pairs 50 \
|
||||
-message-size 500 \
|
||||
-exchange-duration 10m \
|
||||
-message-interval 100ms \
|
||||
-test-duration 15m \
|
||||
-log-level debug
|
||||
|
||||
# Show help
|
||||
./signal-loadtest -h
|
||||
```
|
||||
|
||||
**Available Flags:**
|
||||
- `-server`: Signal server URL (default: `http://localhost:10000`)
|
||||
- `-pairs-per-sec`: Peer pairs created per second (default: 10)
|
||||
- `-total-pairs`: Total number of peer pairs (default: 100)
|
||||
- `-message-size`: Message size in bytes (default: 100)
|
||||
- `-test-duration`: Maximum test duration, 0 = unlimited (default: 0)
|
||||
- `-exchange-duration`: Continuous exchange duration per pair, 0 = single message (default: 0)
|
||||
- `-message-interval`: Interval between messages in continuous mode (default: 100ms)
|
||||
- `-log-level`: Log level: trace, debug, info, warn, error (default: info)
|
||||
|
||||
### Running Tests
|
||||
|
||||
```bash
|
||||
|
||||
128
signal/loadtest/cmd/signal-loadtest/integration_test.go
Normal file
128
signal/loadtest/cmd/signal-loadtest/integration_test.go
Normal file
@@ -0,0 +1,128 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.opentelemetry.io/otel"
|
||||
"google.golang.org/grpc"
|
||||
|
||||
"github.com/netbirdio/netbird/shared/signal/proto"
|
||||
"github.com/netbirdio/netbird/signal/server"
|
||||
)
|
||||
|
||||
func TestCLI_SingleMessage(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
grpcServer, serverAddr := startTestSignalServer(t, ctx)
|
||||
defer grpcServer.Stop()
|
||||
|
||||
cmd := exec.Command("go", "run", "main.go",
|
||||
"-server", serverAddr,
|
||||
"-pairs-per-sec", "3",
|
||||
"-total-pairs", "5",
|
||||
"-message-size", "50",
|
||||
"-log-level", "warn")
|
||||
|
||||
output, err := cmd.CombinedOutput()
|
||||
require.NoError(t, err, "CLI should execute successfully")
|
||||
|
||||
outputStr := string(output)
|
||||
require.Contains(t, outputStr, "Load Test Report")
|
||||
require.Contains(t, outputStr, "Total Pairs Sent: 5")
|
||||
require.Contains(t, outputStr, "Successful Exchanges: 5")
|
||||
t.Logf("Output:\n%s", outputStr)
|
||||
}
|
||||
|
||||
func TestCLI_ContinuousExchange(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("Skipping continuous exchange CLI test in short mode")
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
grpcServer, serverAddr := startTestSignalServer(t, ctx)
|
||||
defer grpcServer.Stop()
|
||||
|
||||
cmd := exec.Command("go", "run", "main.go",
|
||||
"-server", serverAddr,
|
||||
"-pairs-per-sec", "2",
|
||||
"-total-pairs", "3",
|
||||
"-message-size", "100",
|
||||
"-exchange-duration", "3s",
|
||||
"-message-interval", "100ms",
|
||||
"-log-level", "warn")
|
||||
|
||||
output, err := cmd.CombinedOutput()
|
||||
require.NoError(t, err, "CLI should execute successfully")
|
||||
|
||||
outputStr := string(output)
|
||||
require.Contains(t, outputStr, "Load Test Report")
|
||||
require.Contains(t, outputStr, "Total Pairs Sent: 3")
|
||||
require.Contains(t, outputStr, "Successful Exchanges: 3")
|
||||
t.Logf("Output:\n%s", outputStr)
|
||||
}
|
||||
|
||||
func TestCLI_InvalidConfig(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
args []string
|
||||
}{
|
||||
{
|
||||
name: "negative pairs",
|
||||
args: []string{"-pairs-per-sec", "-1"},
|
||||
},
|
||||
{
|
||||
name: "zero total pairs",
|
||||
args: []string{"-total-pairs", "0"},
|
||||
},
|
||||
{
|
||||
name: "negative message size",
|
||||
args: []string{"-message-size", "-100"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
args := append([]string{"run", "main.go"}, tt.args...)
|
||||
cmd := exec.Command("go", args...)
|
||||
output, err := cmd.CombinedOutput()
|
||||
require.Error(t, err, "Should fail with invalid config")
|
||||
require.Contains(t, string(output), "Configuration error")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func startTestSignalServer(t *testing.T, ctx context.Context) (*grpc.Server, string) {
|
||||
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
require.NoError(t, err)
|
||||
|
||||
grpcServer := grpc.NewServer()
|
||||
|
||||
signalServer, err := server.NewServer(ctx, otel.Meter("cli-test"))
|
||||
require.NoError(t, err)
|
||||
|
||||
proto.RegisterSignalExchangeServer(grpcServer, signalServer)
|
||||
|
||||
go func() {
|
||||
if err := grpcServer.Serve(listener); err != nil {
|
||||
t.Logf("Server stopped: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
return grpcServer, fmt.Sprintf("http://%s", listener.Addr().String())
|
||||
}
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
105
signal/loadtest/cmd/signal-loadtest/main.go
Normal file
105
signal/loadtest/cmd/signal-loadtest/main.go
Normal file
@@ -0,0 +1,105 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/netbirdio/netbird/signal/loadtest"
|
||||
)
|
||||
|
||||
var (
|
||||
serverURL string
|
||||
pairsPerSecond int
|
||||
totalPairs int
|
||||
messageSize int
|
||||
testDuration time.Duration
|
||||
exchangeDuration time.Duration
|
||||
messageInterval time.Duration
|
||||
logLevel string
|
||||
)
|
||||
|
||||
func init() {
|
||||
flag.StringVar(&serverURL, "server", "http://localhost:10000", "Signal server URL")
|
||||
flag.IntVar(&pairsPerSecond, "pairs-per-sec", 10, "Number of peer pairs to create per second")
|
||||
flag.IntVar(&totalPairs, "total-pairs", 100, "Total number of peer pairs to create")
|
||||
flag.IntVar(&messageSize, "message-size", 100, "Size of test message in bytes")
|
||||
flag.DurationVar(&testDuration, "test-duration", 0, "Maximum test duration (0 = unlimited)")
|
||||
flag.DurationVar(&exchangeDuration, "exchange-duration", 0, "Duration for continuous message exchange per pair (0 = single message)")
|
||||
flag.DurationVar(&messageInterval, "message-interval", 100*time.Millisecond, "Interval between messages in continuous mode")
|
||||
flag.StringVar(&logLevel, "log-level", "info", "Log level (trace, debug, info, warn, error)")
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
level, err := log.ParseLevel(logLevel)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Invalid log level: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
log.SetLevel(level)
|
||||
|
||||
config := loadtest.LoadTestConfig{
|
||||
ServerURL: serverURL,
|
||||
PairsPerSecond: pairsPerSecond,
|
||||
TotalPairs: totalPairs,
|
||||
MessageSize: messageSize,
|
||||
TestDuration: testDuration,
|
||||
ExchangeDuration: exchangeDuration,
|
||||
MessageInterval: messageInterval,
|
||||
}
|
||||
|
||||
if err := validateConfig(config); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Configuration error: %v\n", err)
|
||||
flag.Usage()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
log.Infof("Signal Load Test Configuration:")
|
||||
log.Infof(" Server URL: %s", config.ServerURL)
|
||||
log.Infof(" Pairs per second: %d", config.PairsPerSecond)
|
||||
log.Infof(" Total pairs: %d", config.TotalPairs)
|
||||
log.Infof(" Message size: %d bytes", config.MessageSize)
|
||||
if config.TestDuration > 0 {
|
||||
log.Infof(" Test duration: %v", config.TestDuration)
|
||||
}
|
||||
if config.ExchangeDuration > 0 {
|
||||
log.Infof(" Exchange duration: %v", config.ExchangeDuration)
|
||||
log.Infof(" Message interval: %v", config.MessageInterval)
|
||||
} else {
|
||||
log.Infof(" Mode: Single message exchange")
|
||||
}
|
||||
fmt.Println()
|
||||
|
||||
lt := loadtest.NewLoadTest(config)
|
||||
if err := lt.Run(); err != nil {
|
||||
log.Errorf("Load test failed: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
metrics := lt.GetMetrics()
|
||||
metrics.PrintReport()
|
||||
}
|
||||
|
||||
func validateConfig(config loadtest.LoadTestConfig) error {
|
||||
if config.ServerURL == "" {
|
||||
return fmt.Errorf("server URL is required")
|
||||
}
|
||||
if config.PairsPerSecond <= 0 {
|
||||
return fmt.Errorf("pairs-per-sec must be greater than 0")
|
||||
}
|
||||
if config.TotalPairs <= 0 {
|
||||
return fmt.Errorf("total-pairs must be greater than 0")
|
||||
}
|
||||
if config.MessageSize <= 0 {
|
||||
return fmt.Errorf("message-size must be greater than 0")
|
||||
}
|
||||
if config.MessageInterval <= 0 {
|
||||
return fmt.Errorf("message-interval must be greater than 0")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
28
signal/loadtest/cmd/signal-loadtest/test.sh
Normal file
28
signal/loadtest/cmd/signal-loadtest/test.sh
Normal file
@@ -0,0 +1,28 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
echo "Building signal-loadtest binary..."
|
||||
go build -o signal-loadtest
|
||||
|
||||
echo ""
|
||||
echo "=== Test 1: Single message exchange (5 pairs) ==="
|
||||
./signal-loadtest \
|
||||
-server http://localhost:10000 \
|
||||
-pairs-per-sec 5 \
|
||||
-total-pairs 5 \
|
||||
-message-size 50 \
|
||||
-log-level info
|
||||
|
||||
echo ""
|
||||
echo "=== Test 2: Continuous exchange (3 pairs, 5 seconds) ==="
|
||||
./signal-loadtest \
|
||||
-server http://localhost:10000 \
|
||||
-pairs-per-sec 3 \
|
||||
-total-pairs 3 \
|
||||
-message-size 100 \
|
||||
-exchange-duration 5s \
|
||||
-message-interval 200ms \
|
||||
-log-level info
|
||||
|
||||
echo ""
|
||||
echo "All tests completed!"
|
||||
Reference in New Issue
Block a user