feat: Docker Compose E2E test env, agent fixes, TAP MAC config, testclient binary

- Add docker-compose.yml with server + init + 3 clients + tester
- Add Dockerfile.server (CGO multi-stage) and Dockerfile.client
- Add .dockerignore for efficient builds
- Add cmd/testclient/main.go: full E2E test binary using SDK
- Fix agent handshake loop: only respond to new handshakes
- Add tapInterface.SetMAC() to match agent's generated MAC
- Call SetMAC() in agent SetTAPIP to fix ARP resolution
- Add data-plane debug logging for troubleshooting
- Change config database path to /tmp/data/ for container use
- Add SetMAC stub for non-Linux builds
This commit is contained in:
xieyao
2026-06-17 05:04:19 +08:00
parent e3147ac06f
commit 807c4cf323
9 changed files with 535 additions and 8 deletions

192
cmd/testclient/main.go Normal file
View File

@@ -0,0 +1,192 @@
package main
import (
"flag"
"fmt"
"log"
"log/slog"
"net"
"os"
"os/signal"
"strings"
"syscall"
"time"
"zeromesh/sdk"
)
var (
serverURL = flag.String("server", "http://server:10001", "Controller HTTP URL")
name = flag.String("name", "client", "Node name")
udpPort = flag.Int("udp", 7001, "Agent UDP listen port")
tapName = flag.String("tap", "ztmesh0", "TAP device name")
identityPath = flag.String("identity", "", "Identity file path")
adminUser = flag.String("admin", "admin", "Admin username")
adminPass = flag.String("pass", "admin123456", "Admin password")
)
func main() {
flag.Parse()
slog.SetLogLoggerLevel(slog.LevelInfo)
cfg := sdk.DefaultConfig()
cfg.ControllerURL = *serverURL
cfg.ListenPort = *udpPort
cfg.TapName = *tapName
cfg.PSK = "default-psk-change-me"
if *identityPath != "" {
cfg.IdentityPath = *identityPath
}
cli := sdk.New(cfg)
var token string
for i := 0; i < 30; i++ {
if err := cli.Login(*adminUser, *adminPass); err == nil {
token = cli.Token()
break
}
if _, err := cli.InitAdmin(*adminUser, *adminPass); err == nil {
token = cli.Token()
log.Print("initialized admin")
break
}
log.Printf("waiting for server (attempt %d)...", i+1)
time.Sleep(2 * time.Second)
}
if token == "" {
log.Fatal("could not login or init admin after 30 attempts")
}
log.Printf("logged in as %s", *adminUser)
idPath := *identityPath
if idPath == "" {
idPath = fmt.Sprintf("/tmp/%s.id", *name)
}
id, err := sdk.LoadOrGenerateIdentity(idPath)
if err != nil {
log.Fatalf("identity: %v", err)
}
log.Printf("identity: %s pub=%s", id.Address, id.PublicKeyHex())
myIP := sdk.GetPreferredIP()
node, err := cli.RegisterNode(id.Address.String(), id.PublicKeyHex(), *name, myIP, *udpPort, "1.0.0")
if err != nil {
log.Fatalf("register node: %v", err)
}
log.Printf("registered node: %s", node.NodeID)
findNetwork := func() *sdk.Network {
networks, err := cli.ListNetworks()
if err != nil {
return nil
}
for i := range networks {
if !strings.HasPrefix(networks[i].IPRange, "10.7.") && !strings.HasPrefix(networks[i].IPRange, "172.") && !strings.HasPrefix(networks[i].IPRange, "192.") {
continue
}
return &networks[i]
}
if len(networks) > 0 {
return &networks[0]
}
return nil
}
myNet := findNetwork()
if myNet == nil {
created, err := cli.CreateNetwork(fmt.Sprintf("testnet-%s", *name), "")
if err != nil {
log.Fatalf("create network: %v", err)
}
myNet = created
log.Printf("created network: %s (%s) id=%d", myNet.Name, myNet.IPRange, myNet.NetworkID)
} else {
log.Printf("using network: %s (%s) id=%d", myNet.Name, myNet.IPRange, myNet.NetworkID)
}
var myIPAddr string
for i := 0; i < 10; i++ {
members, err := cli.NetworkMembers(myNet.NetworkID)
if err == nil {
for _, m := range members {
if m.NodeID == id.Address.String() {
myIPAddr = m.IPAddress
break
}
}
}
if myIPAddr != "" {
break
}
log.Printf("authorizing self on network %d...", myNet.NetworkID)
cli.AuthorizeMember(myNet.NetworkID, id.Address.String())
time.Sleep(1 * time.Second)
}
if myIPAddr == "" {
log.Fatalf("could not get virtual IP after authorization")
}
log.Printf("my virtual IP: %s", myIPAddr)
agent := sdk.NewAgent(cli)
agent.SetIdentity(id)
if err := agent.Start(); err != nil {
log.Fatalf("agent start: %v", err)
}
defer agent.Stop()
log.Printf("agent started: tap=%s udp=%d", agent.TAPName(), agent.LocalPort())
agent.ControllerHello()
time.Sleep(2 * time.Second)
for i := 0; i < 6; i++ {
agent.SyncPeers()
time.Sleep(3 * time.Second)
c := agent.PeerCount()
if c > 0 {
break
}
}
cidrFull := myIPAddr + "/24"
if err := agent.SetTAPIP(cidrFull); err != nil {
log.Printf("set tap ip warning: %v", err)
} else {
log.Printf("tap ip set: %s", cidrFull)
}
listener, err := net.Listen("tcp", fmt.Sprintf("%s:7777", myIPAddr))
if err != nil {
log.Printf("echo server listen error (expected if TAP not ready): %v", err)
} else {
defer listener.Close()
log.Printf("echo server listening on %s:7777", myIPAddr)
go func() {
for {
conn, err := listener.Accept()
if err != nil {
return
}
go func(c net.Conn) {
defer c.Close()
buf := make([]byte, 4096)
for {
n, err := c.Read(buf)
if err != nil {
return
}
c.Write(buf[:n])
}
}(conn)
}
}()
}
peerCount := agent.PeerCount()
log.Printf("%s ready: ip=%s tap=%s peers=%d", *name, myIPAddr, agent.TAPName(), peerCount)
sc := make(chan os.Signal, 1)
signal.Notify(sc, syscall.SIGINT, syscall.SIGTERM)
<-sc
log.Printf("shutting down %s", *name)
}