vmtest: add VM-based integration test framework
Add tstest/natlab/vmtest, a high-level framework for running multi-VM
integration tests with mixed OS types (gokrazy + Ubuntu/Debian cloud
images) connected via natlab's vnet virtual network.
The vmtest package provides:
- Env type that orchestrates vnet, QEMU processes, and agent connections
- OS image support (Gokrazy, Ubuntu2404, Debian12) with download/cache
- QEMU launch per OS type (microvm for gokrazy, q35+KVM for cloud)
- Cloud-init seed ISO generation with network-config for multi-NIC
- Cross-compilation of test binaries for cloud VMs
- Debug SSH NIC on cloud VMs for interactive debugging
- Test helpers: ApproveRoutes, HTTPGet, TailscalePing, DumpStatus,
WaitForPeerRoute, SSHExec
TTA enhancements (cmd/tta):
- Parameterize /up (accept-routes, advertise-routes, snat-subnet-routes)
- Add /set, /start-webserver, /http-get endpoints
- /http-get uses local.Client.UserDial for Tailscale-routed requests
- Fix /ping for non-gokrazy systems
TestSubnetRouter exercises a 3-VM subnet router scenario:
client (gokrazy) → subnet-router (Ubuntu, dual-NIC) → backend (gokrazy)
Verifies HTTP access to the backend webserver through the Tailscale
subnet route. Passes in ~30 seconds.
Updates tailscale/tailscale#13038
Change-Id: I165b64af241d37f5f5870e796a52502fc56146fa
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
committed by
Brad Fitzpatrick
parent
d948b78b23
commit
ec0b23a21f
@@ -294,6 +294,24 @@ func stringifyTEI(tei stack.TransportEndpointID) string {
|
||||
return fmt.Sprintf("%s -> %s", remoteHostPort, localHostPort)
|
||||
}
|
||||
|
||||
// vipNameOf returns the VIP name for the given IP, or "" if it's not a VIP.
|
||||
func vipNameOf(ip netip.Addr) string {
|
||||
for _, v := range vips {
|
||||
if v.Match(ip) {
|
||||
return v.name
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// nodeNameOf returns the node's name for the given IP on this network, or "" if unknown.
|
||||
func (n *network) nodeNameOf(ip netip.Addr) string {
|
||||
if node, ok := n.nodeByIP(ip); ok {
|
||||
return node.String()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (n *network) acceptTCP(r *tcp.ForwarderRequest) {
|
||||
reqDetails := r.ID()
|
||||
|
||||
@@ -305,7 +323,17 @@ func (n *network) acceptTCP(r *tcp.ForwarderRequest) {
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("vnet-AcceptTCP: %v", stringifyTEI(reqDetails))
|
||||
// Annotate the log with node/VIP names for readability.
|
||||
srcHP := net.JoinHostPort(clientRemoteIP.String(), strconv.Itoa(int(reqDetails.RemotePort)))
|
||||
srcStr := srcHP
|
||||
if name := n.nodeNameOf(clientRemoteIP); name != "" {
|
||||
srcStr = fmt.Sprintf("%s (%s)", srcHP, name)
|
||||
}
|
||||
dstStr := net.JoinHostPort(destIP.String(), strconv.Itoa(int(destPort)))
|
||||
if name := vipNameOf(destIP); name != "" {
|
||||
dstStr = fmt.Sprintf("%s (%s)", dstStr, name)
|
||||
}
|
||||
log.Printf("vnet-AcceptTCP: %s -> %s", srcStr, dstStr)
|
||||
|
||||
var wq waiter.Queue
|
||||
ep, err := r.CreateEndpoint(&wq)
|
||||
@@ -1466,6 +1494,12 @@ func (n *network) HandleEthernetPacketForRouter(ep EthernetPacket) {
|
||||
return
|
||||
}
|
||||
|
||||
if toForward {
|
||||
// Traffic to destinations we don't handle (e.g. VMs trying to reach
|
||||
// the real internet for NTP, package updates, etc). Expected; drop silently.
|
||||
return
|
||||
}
|
||||
|
||||
n.logf("router got unknown packet: %v", packet)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user