tstest/natlab: add TestSubnetRouterFreeBSD with FreeBSD cloud image support
As a warm-up to making natlab support multiple operating systems, start with an easy one (in that it's also Unixy and open source like Linux) and add FreeBSD 15.0 as a VM OS option for the vmtest integration test framework, and add TestSubnetRouterFreeBSD which tests subnet routing through a FreeBSD VM (Gokrazy → FreeBSD → Gokrazy). Key changes: - Add FreeBSD150 OSImage using the official FreeBSD 15.0 BASIC-CLOUDINIT cloud image (xz-compressed qcow2) - Add GOOS()/IsFreeBSD() methods to OSImage for cross-compilation and OS-specific behavior - Handle xz-compressed image downloads in ensureImage - Refactor compileBinaries into compileBinariesForOS to support multiple GOOS targets (linux, freebsd), with binaries registered at <goos>/<name> paths on the file server VIP - Add FreeBSD-specific cloud-init (nuageinit) user-data generation: string-form runcmd (nuageinit doesn't support YAML arrays), fetch(1) instead of curl, FreeBSD sysctl names for IP forwarding, mkdir /usr/local/bin, PATH setup for tta - Skip network-config in cidata ISO for FreeBSD (DHCP via rc.conf) Updates tailscale/tailscale#13038 Change-Id: Ibeb4f7d02659d5cd8e3a7c3a66ee7b1a92a0110d Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
committed by
Brad Fitzpatrick
parent
85d6ba9473
commit
dca1d8eea1
@@ -26,7 +26,6 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -185,17 +184,22 @@ func (e *Env) Start() {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Determine if we have any non-gokrazy "cloud" images (e.g. Ubuntu, Debian)
|
||||
// that require compiled binaries pushed into their image later. (Gokrazy
|
||||
// has them built-in, so doesn't need the compileBinaries step.)
|
||||
needBuildBinaries := slices.ContainsFunc(e.nodes, func(n *Node) bool { return !n.os.IsGokrazy })
|
||||
// Determine which GOOS/GOARCH pairs need compiled binaries (non-gokrazy
|
||||
// images). Gokrazy has binaries built-in, so doesn't need compilation.
|
||||
type platform struct{ goos, goarch string }
|
||||
needPlatform := set.Set[platform]{}
|
||||
for _, n := range e.nodes {
|
||||
if !n.os.IsGokrazy {
|
||||
needPlatform.Add(platform{n.os.GOOS(), n.os.GOARCH()})
|
||||
}
|
||||
}
|
||||
|
||||
// Compile binaries and download/build images in parallel.
|
||||
// Any failure cancels the others via the errgroup context.
|
||||
eg, egCtx := errgroup.WithContext(ctx)
|
||||
if needBuildBinaries {
|
||||
for _, p := range needPlatform.Slice() {
|
||||
eg.Go(func() error {
|
||||
return e.compileBinaries(egCtx)
|
||||
return e.compileBinariesForOS(egCtx, p.goos, p.goarch)
|
||||
})
|
||||
}
|
||||
didOS := set.Set[string]{} // dedup by image name
|
||||
@@ -227,13 +231,15 @@ func (e *Env) Start() {
|
||||
t.Cleanup(func() { e.server.Close() })
|
||||
|
||||
// Register compiled binaries with the file server VIP.
|
||||
if needBuildBinaries {
|
||||
// Binaries are registered at <goos>_<goarch>/<name> (e.g. "linux_amd64/tta").
|
||||
for _, p := range needPlatform.Slice() {
|
||||
dir := p.goos + "_" + p.goarch
|
||||
for _, name := range []string{"tta", "tailscale", "tailscaled"} {
|
||||
data, err := os.ReadFile(filepath.Join(e.binDir, name))
|
||||
data, err := os.ReadFile(filepath.Join(e.binDir, dir, name))
|
||||
if err != nil {
|
||||
t.Fatalf("reading compiled %s: %v", name, err)
|
||||
t.Fatalf("reading compiled %s/%s: %v", dir, name, err)
|
||||
}
|
||||
e.server.RegisterFile(name, data)
|
||||
e.server.RegisterFile(dir+"/"+name, data)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -603,14 +609,20 @@ func (e *Env) ensureGokrazy(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// compileBinaries cross-compiles tta, tailscale, and tailscaled for linux/amd64
|
||||
// and places them in e.binDir.
|
||||
func (e *Env) compileBinaries(ctx context.Context) error {
|
||||
// compileBinariesForOS cross-compiles tta, tailscale, and tailscaled for the
|
||||
// given GOOS/GOARCH and places them in e.binDir/<goos>_<goarch>/.
|
||||
func (e *Env) compileBinariesForOS(ctx context.Context, goos, goarch string) error {
|
||||
modRoot, err := findModRoot()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dir := goos + "_" + goarch
|
||||
outDir := filepath.Join(e.binDir, dir)
|
||||
if err := os.MkdirAll(outDir, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
binaries := []struct{ name, pkg string }{
|
||||
{"tta", "./cmd/tta"},
|
||||
{"tailscale", "./cmd/tailscale"},
|
||||
@@ -620,15 +632,15 @@ func (e *Env) compileBinaries(ctx context.Context) error {
|
||||
var eg errgroup.Group
|
||||
for _, bin := range binaries {
|
||||
eg.Go(func() error {
|
||||
outPath := filepath.Join(e.binDir, bin.name)
|
||||
e.t.Logf("compiling %s...", bin.name)
|
||||
outPath := filepath.Join(outDir, bin.name)
|
||||
e.t.Logf("compiling %s/%s...", dir, bin.name)
|
||||
cmd := exec.CommandContext(ctx, "go", "build", "-o", outPath, bin.pkg)
|
||||
cmd.Dir = modRoot
|
||||
cmd.Env = append(os.Environ(), "GOOS=linux", "GOARCH=amd64", "CGO_ENABLED=0")
|
||||
cmd.Env = append(os.Environ(), "GOOS="+goos, "GOARCH="+goarch, "CGO_ENABLED=0")
|
||||
if out, err := cmd.CombinedOutput(); err != nil {
|
||||
return fmt.Errorf("building %s: %v\n%s", bin.name, err, out)
|
||||
return fmt.Errorf("building %s/%s: %v\n%s", dir, bin.name, err, out)
|
||||
}
|
||||
e.t.Logf("compiled %s", bin.name)
|
||||
e.t.Logf("compiled %s/%s", dir, bin.name)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user