tstest/natlab/vmtest: add --test-version flag
Add a --test-version flag to run the natlab VM tests against released tailscale/tailscaled binaries downloaded from pkgs.tailscale.com instead of building from the source tree. The value can be a concrete release like "1.97.255", or "stable" / "unstable" which resolve to the latest TarballsVersion on that track via pkgs.tailscale.com/<track>/?mode=json. The track for a concrete version is derived from its minor (even=stable, odd=unstable). The host architecture (amd64 or arm64) selects the tarball. Tarballs are cached + extracted under ~/.cache/tailscale-vmtest/builds/<version>_<arch>/ so they are not re-fetched per test. tta is still always built from the local tree. Cloud VMs (Ubuntu, Debian) pick up the downloaded binaries via the existing files.tailscale file server. Non-Linux GOOS (FreeBSD) falls back to building from source since pkgs.tailscale.com only ships Linux tarballs. Gokrazy nodes continue to use binaries baked into the gokrazy image; --test-version is a no-op for them. Updates #13038 Change-Id: I213ef7db362dd17bf69d2685cbf2ab0ec5a3fee1 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
committed by
Brad Fitzpatrick
parent
7735b15de3
commit
cb239808a6
@@ -42,6 +42,7 @@ import (
|
||||
var (
|
||||
runVMTests = flag.Bool("run-vm-tests", false, "run tests that require VMs with KVM")
|
||||
verboseVMDebug = flag.Bool("verbose-vm-debug", false, "enable verbose debug logging for VM tests")
|
||||
testVersion = flag.String("test-version", "", `if non-empty, download tailscale & tailscaled at the given release version (e.g. "1.97.255", "unstable", or "stable") instead of building from the source tree`)
|
||||
)
|
||||
|
||||
// Env is a test environment that manages virtual networks and QEMU VMs.
|
||||
@@ -56,6 +57,11 @@ type Env struct {
|
||||
sockAddr string // shared Unix socket path for all QEMU netdevs
|
||||
binDir string // directory for compiled binaries
|
||||
|
||||
// testVersion is the resolved Tailscale release version to use (empty if
|
||||
// building from source). When non-empty, tailscale and tailscaled binaries
|
||||
// are downloaded from pkgs.tailscale.com instead of compiled from the tree.
|
||||
testVersion string
|
||||
|
||||
// gokrazy-specific paths
|
||||
gokrazyBase string // path to gokrazy base qcow2 image
|
||||
gokrazyKernel string // path to gokrazy kernel
|
||||
@@ -196,6 +202,17 @@ func (e *Env) Start() {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Resolve --test-version up front (e.g. "unstable" -> "1.97.255") so all
|
||||
// platforms see the same concrete version.
|
||||
if *testVersion != "" {
|
||||
v, err := resolveTestVersion(ctx, *testVersion)
|
||||
if err != nil {
|
||||
t.Fatalf("resolving --test-version=%q: %v", *testVersion, err)
|
||||
}
|
||||
e.testVersion = v
|
||||
t.Logf("using Tailscale release version %s (from --test-version=%q)", v, *testVersion)
|
||||
}
|
||||
|
||||
// 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 }
|
||||
@@ -716,8 +733,13 @@ func (e *Env) ensureGokrazy(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// compileBinariesForOS cross-compiles tta, tailscale, and tailscaled for the
|
||||
// given GOOS/GOARCH and places them in e.binDir/<goos>_<goarch>/.
|
||||
// compileBinariesForOS prepares the tta, tailscale, and tailscaled binaries
|
||||
// for the given GOOS/GOARCH and places them in e.binDir/<goos>_<goarch>/.
|
||||
//
|
||||
// tta is always built from the local source tree (the test agent must match
|
||||
// the test framework). When --test-version is set, tailscale and tailscaled
|
||||
// are taken from the downloaded release tarball instead of being compiled
|
||||
// from source.
|
||||
func (e *Env) compileBinariesForOS(ctx context.Context, goos, goarch string) error {
|
||||
modRoot, err := findModRoot()
|
||||
if err != nil {
|
||||
@@ -730,14 +752,20 @@ func (e *Env) compileBinariesForOS(ctx context.Context, goos, goarch string) err
|
||||
return err
|
||||
}
|
||||
|
||||
binaries := []struct{ name, pkg string }{
|
||||
{"tta", "./cmd/tta"},
|
||||
{"tailscale", "./cmd/tailscale"},
|
||||
{"tailscaled", "./cmd/tailscaled"},
|
||||
// Use downloaded release binaries only on Linux: pkgs.tailscale.com only
|
||||
// publishes Linux tarballs, so other GOOS values still build from source.
|
||||
useDownloaded := e.testVersion != "" && goos == "linux"
|
||||
|
||||
type binary struct{ name, pkg string }
|
||||
buildBins := []binary{{"tta", "./cmd/tta"}}
|
||||
if !useDownloaded {
|
||||
buildBins = append(buildBins,
|
||||
binary{"tailscale", "./cmd/tailscale"},
|
||||
binary{"tailscaled", "./cmd/tailscaled"})
|
||||
}
|
||||
|
||||
var eg errgroup.Group
|
||||
for _, bin := range binaries {
|
||||
for _, bin := range buildBins {
|
||||
eg.Go(func() error {
|
||||
outPath := filepath.Join(outDir, bin.name)
|
||||
e.t.Logf("compiling %s/%s...", dir, bin.name)
|
||||
@@ -751,9 +779,36 @@ func (e *Env) compileBinariesForOS(ctx context.Context, goos, goarch string) err
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
if useDownloaded {
|
||||
eg.Go(func() error {
|
||||
srcDir, err := ensureVersionBinaries(ctx, e.testVersion, goarch, e.t.Logf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, name := range []string{"tailscale", "tailscaled"} {
|
||||
if err := copyFile(filepath.Join(srcDir, name), filepath.Join(outDir, name), 0755); err != nil {
|
||||
return fmt.Errorf("staging %s/%s: %w", dir, name, err)
|
||||
}
|
||||
}
|
||||
e.t.Logf("staged version %s tailscale & tailscaled for %s", e.testVersion, dir)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
return eg.Wait()
|
||||
}
|
||||
|
||||
// copyFile copies src to dst with the given permission bits.
|
||||
func copyFile(src, dst string, perm os.FileMode) error {
|
||||
in, err := os.Open(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer in.Close()
|
||||
return writeAtomic(dst, in, perm)
|
||||
}
|
||||
|
||||
// findModRoot returns the root of the Go module (where go.mod is).
|
||||
func findModRoot() (string, error) {
|
||||
out, err := exec.Command("go", "env", "GOMOD").CombinedOutput()
|
||||
|
||||
Reference in New Issue
Block a user