mirror of
https://github.com/tailscale/tailscale.git
synced 2024-11-29 13:05:46 +00:00
tstest/integration/vms: use one testcontrol instance per VM (#2437)
This paves the way for future MagicDNS tests. Signed-off-by: Christine Dodrill <xe@tailscale.com>
This commit is contained in:
parent
171ec9f8f4
commit
391207bbcf
@ -10,14 +10,19 @@
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
"net"
|
"net"
|
||||||
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gliderlabs/ssh"
|
"golang.org/x/crypto/ssh"
|
||||||
"golang.org/x/net/proxy"
|
"golang.org/x/net/proxy"
|
||||||
"inet.af/netaddr"
|
"inet.af/netaddr"
|
||||||
"tailscale.com/tstest/integration"
|
"tailscale.com/tstest/integration"
|
||||||
@ -28,10 +33,102 @@ type Harness struct {
|
|||||||
testerDialer proxy.Dialer
|
testerDialer proxy.Dialer
|
||||||
testerDir string
|
testerDir string
|
||||||
bins *integration.Binaries
|
bins *integration.Binaries
|
||||||
|
pubKey string
|
||||||
signer ssh.Signer
|
signer ssh.Signer
|
||||||
cs *testcontrol.Server
|
cs *testcontrol.Server
|
||||||
loginServerURL string
|
loginServerURL string
|
||||||
testerV4 netaddr.IP
|
testerV4 netaddr.IP
|
||||||
|
ipMu *sync.Mutex
|
||||||
|
ipMap map[string]ipMapping
|
||||||
|
}
|
||||||
|
|
||||||
|
func newHarness(t *testing.T) *Harness {
|
||||||
|
dir := t.TempDir()
|
||||||
|
bindHost := deriveBindhost(t)
|
||||||
|
ln, err := net.Listen("tcp", net.JoinHostPort(bindHost, "0"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("can't make TCP listener: %v", err)
|
||||||
|
}
|
||||||
|
t.Cleanup(func() {
|
||||||
|
ln.Close()
|
||||||
|
})
|
||||||
|
t.Logf("host:port: %s", ln.Addr())
|
||||||
|
|
||||||
|
cs := &testcontrol.Server{}
|
||||||
|
|
||||||
|
derpMap := integration.RunDERPAndSTUN(t, t.Logf, bindHost)
|
||||||
|
cs.DERPMap = derpMap
|
||||||
|
|
||||||
|
var (
|
||||||
|
ipMu sync.Mutex
|
||||||
|
ipMap = map[string]ipMapping{}
|
||||||
|
)
|
||||||
|
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
mux.Handle("/", cs)
|
||||||
|
|
||||||
|
lc := &integration.LogCatcher{}
|
||||||
|
if *verboseLogcatcher {
|
||||||
|
lc.UseLogf(t.Logf)
|
||||||
|
}
|
||||||
|
mux.Handle("/c/", lc)
|
||||||
|
|
||||||
|
// This handler will let the virtual machines tell the host information about that VM.
|
||||||
|
// This is used to maintain a list of port->IP address mappings that are known to be
|
||||||
|
// working. This allows later steps to connect over SSH. This returns no response to
|
||||||
|
// clients because no response is needed.
|
||||||
|
mux.HandleFunc("/myip/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ipMu.Lock()
|
||||||
|
defer ipMu.Unlock()
|
||||||
|
|
||||||
|
name := path.Base(r.URL.Path)
|
||||||
|
host, _, _ := net.SplitHostPort(r.RemoteAddr)
|
||||||
|
port, err := strconv.Atoi(name)
|
||||||
|
if err != nil {
|
||||||
|
log.Panicf("bad port: %v", port)
|
||||||
|
}
|
||||||
|
distro := r.UserAgent()
|
||||||
|
ipMap[distro] = ipMapping{distro, port, host}
|
||||||
|
t.Logf("%s: %v", name, host)
|
||||||
|
})
|
||||||
|
|
||||||
|
hs := &http.Server{Handler: mux}
|
||||||
|
go hs.Serve(ln)
|
||||||
|
|
||||||
|
run(t, dir, "ssh-keygen", "-t", "ed25519", "-f", "machinekey", "-N", ``)
|
||||||
|
pubkey, err := os.ReadFile(filepath.Join(dir, "machinekey.pub"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("can't read ssh key: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
privateKey, err := os.ReadFile(filepath.Join(dir, "machinekey"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("can't read ssh private key: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
signer, err := ssh.ParsePrivateKey(privateKey)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("can't parse private key: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
loginServer := fmt.Sprintf("http://%s", ln.Addr())
|
||||||
|
t.Logf("loginServer: %s", loginServer)
|
||||||
|
|
||||||
|
bins := integration.BuildTestBinaries(t)
|
||||||
|
|
||||||
|
h := &Harness{
|
||||||
|
pubKey: string(pubkey),
|
||||||
|
bins: bins,
|
||||||
|
signer: signer,
|
||||||
|
loginServerURL: loginServer,
|
||||||
|
cs: cs,
|
||||||
|
ipMu: &ipMu,
|
||||||
|
ipMap: ipMap,
|
||||||
|
}
|
||||||
|
|
||||||
|
h.makeTestNode(t, bins, loginServer)
|
||||||
|
|
||||||
|
return h
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Harness) Tailscale(t *testing.T, args ...string) []byte {
|
func (h *Harness) Tailscale(t *testing.T, args ...string) []byte {
|
||||||
|
@ -166,7 +166,7 @@ func copyUnit(t *testing.T, bins *integration.Binaries) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h Harness) makeNixOSImage(t *testing.T, d Distro, cdir string) string {
|
func (h *Harness) makeNixOSImage(t *testing.T, d Distro, cdir string) string {
|
||||||
copyUnit(t, h.bins)
|
copyUnit(t, h.bins)
|
||||||
dir := t.TempDir()
|
dir := t.TempDir()
|
||||||
fname := filepath.Join(dir, d.name+".nix")
|
fname := filepath.Join(dir, d.name+".nix")
|
||||||
|
@ -35,7 +35,7 @@
|
|||||||
// mkVM makes a KVM-accelerated virtual machine and prepares it for introduction
|
// mkVM makes a KVM-accelerated virtual machine and prepares it for introduction
|
||||||
// to the testcontrol server. The function it returns is for killing the virtual
|
// to the testcontrol server. The function it returns is for killing the virtual
|
||||||
// machine when it is time for it to die.
|
// machine when it is time for it to die.
|
||||||
func (h Harness) mkVM(t *testing.T, n int, d Distro, sshKey, hostURL, tdir string) {
|
func (h *Harness) mkVM(t *testing.T, n int, d Distro, sshKey, hostURL, tdir string) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
cdir, err := os.UserCacheDir()
|
cdir, err := os.UserCacheDir()
|
||||||
@ -160,7 +160,7 @@ func fetchFromS3(t *testing.T, fout *os.File, d Distro) bool {
|
|||||||
|
|
||||||
// fetchDistro fetches a distribution from the internet if it doesn't already exist locally. It
|
// fetchDistro fetches a distribution from the internet if it doesn't already exist locally. It
|
||||||
// also validates the sha256 sum from a known good hash.
|
// also validates the sha256 sum from a known good hash.
|
||||||
func (h Harness) fetchDistro(t *testing.T, resultDistro Distro) string {
|
func (h *Harness) fetchDistro(t *testing.T, resultDistro Distro) string {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
cdir, err := os.UserCacheDir()
|
cdir, err := os.UserCacheDir()
|
||||||
@ -253,7 +253,7 @@ func checkCachedImageHash(t *testing.T, d Distro, cacheDir string) (gotHash stri
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h Harness) copyBinaries(t *testing.T, d Distro, conn *ssh.Client) {
|
func (h *Harness) copyBinaries(t *testing.T, d Distro, conn *ssh.Client) {
|
||||||
bins := h.bins
|
bins := h.bins
|
||||||
if strings.HasPrefix(d.name, "nixos") {
|
if strings.HasPrefix(d.name, "nixos") {
|
||||||
return
|
return
|
||||||
|
@ -39,7 +39,7 @@ func retry(t *testing.T, fn func() error) {
|
|||||||
t.Fatalf("tried %d times, got: %v", tries, err)
|
t.Fatalf("tried %d times, got: %v", tries, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h Harness) testPing(t *testing.T, ipAddr netaddr.IP, cli *ssh.Client) {
|
func (h *Harness) testPing(t *testing.T, ipAddr netaddr.IP, cli *ssh.Client) {
|
||||||
var outp []byte
|
var outp []byte
|
||||||
var err error
|
var err error
|
||||||
retry(t, func() error {
|
retry(t, func() error {
|
||||||
@ -83,7 +83,7 @@ func getSession(t *testing.T, cli *ssh.Client) *ssh.Session {
|
|||||||
return sess
|
return sess
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h Harness) testOutgoingTCP(t *testing.T, ipAddr netaddr.IP, cli *ssh.Client) {
|
func (h *Harness) testOutgoingTCP(t *testing.T, ipAddr netaddr.IP, cli *ssh.Client) {
|
||||||
const sendmsg = "this is a message that curl won't print"
|
const sendmsg = "this is a message that curl won't print"
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
s := &http.Server{
|
s := &http.Server{
|
||||||
|
@ -11,17 +11,13 @@
|
|||||||
"context"
|
"context"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
"testing"
|
"testing"
|
||||||
"text/template"
|
"text/template"
|
||||||
"time"
|
"time"
|
||||||
@ -33,7 +29,6 @@
|
|||||||
"inet.af/netaddr"
|
"inet.af/netaddr"
|
||||||
"tailscale.com/tstest"
|
"tailscale.com/tstest"
|
||||||
"tailscale.com/tstest/integration"
|
"tailscale.com/tstest/integration"
|
||||||
"tailscale.com/tstest/integration/testcontrol"
|
|
||||||
"tailscale.com/types/logger"
|
"tailscale.com/types/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -75,7 +70,7 @@ func TestDownloadImages(t *testing.T) {
|
|||||||
|
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
(Harness{bins: bins}).fetchDistro(t, distro)
|
(&Harness{bins: bins}).fetchDistro(t, distro)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -245,89 +240,8 @@ func TestVMIntegrationEndToEnd(t *testing.T) {
|
|||||||
t.Fatalf("missing dependency: %v", err)
|
t.Fatalf("missing dependency: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
dir := t.TempDir()
|
|
||||||
|
|
||||||
rex := distroRex.Unwrap()
|
|
||||||
|
|
||||||
bindHost := deriveBindhost(t)
|
|
||||||
ln, err := net.Listen("tcp", net.JoinHostPort(bindHost, "0"))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("can't make TCP listener: %v", err)
|
|
||||||
}
|
|
||||||
defer ln.Close()
|
|
||||||
t.Logf("host:port: %s", ln.Addr())
|
|
||||||
|
|
||||||
cs := &testcontrol.Server{}
|
|
||||||
|
|
||||||
derpMap := integration.RunDERPAndSTUN(t, t.Logf, bindHost)
|
|
||||||
cs.DERPMap = derpMap
|
|
||||||
|
|
||||||
var (
|
|
||||||
ipMu sync.Mutex
|
|
||||||
ipMap = map[string]ipMapping{}
|
|
||||||
)
|
|
||||||
|
|
||||||
mux := http.NewServeMux()
|
|
||||||
mux.Handle("/", cs)
|
|
||||||
|
|
||||||
lc := &integration.LogCatcher{}
|
|
||||||
if *verboseLogcatcher {
|
|
||||||
lc.UseLogf(t.Logf)
|
|
||||||
}
|
|
||||||
mux.Handle("/c/", lc)
|
|
||||||
|
|
||||||
// This handler will let the virtual machines tell the host information about that VM.
|
|
||||||
// This is used to maintain a list of port->IP address mappings that are known to be
|
|
||||||
// working. This allows later steps to connect over SSH. This returns no response to
|
|
||||||
// clients because no response is needed.
|
|
||||||
mux.HandleFunc("/myip/", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
ipMu.Lock()
|
|
||||||
defer ipMu.Unlock()
|
|
||||||
|
|
||||||
name := path.Base(r.URL.Path)
|
|
||||||
host, _, _ := net.SplitHostPort(r.RemoteAddr)
|
|
||||||
port, err := strconv.Atoi(name)
|
|
||||||
if err != nil {
|
|
||||||
log.Panicf("bad port: %v", port)
|
|
||||||
}
|
|
||||||
distro := r.UserAgent()
|
|
||||||
ipMap[distro] = ipMapping{distro, port, host}
|
|
||||||
t.Logf("%s: %v", name, host)
|
|
||||||
})
|
|
||||||
|
|
||||||
hs := &http.Server{Handler: mux}
|
|
||||||
go hs.Serve(ln)
|
|
||||||
|
|
||||||
run(t, dir, "ssh-keygen", "-t", "ed25519", "-f", "machinekey", "-N", ``)
|
|
||||||
pubkey, err := os.ReadFile(filepath.Join(dir, "machinekey.pub"))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("can't read ssh key: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
privateKey, err := os.ReadFile(filepath.Join(dir, "machinekey"))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("can't read ssh private key: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
signer, err := ssh.ParsePrivateKey(privateKey)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("can't parse private key: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
loginServer := fmt.Sprintf("http://%s", ln.Addr())
|
|
||||||
t.Logf("loginServer: %s", loginServer)
|
|
||||||
|
|
||||||
ramsem := semaphore.NewWeighted(int64(*vmRamLimit))
|
ramsem := semaphore.NewWeighted(int64(*vmRamLimit))
|
||||||
bins := integration.BuildTestBinaries(t)
|
rex := distroRex.Unwrap()
|
||||||
|
|
||||||
h := &Harness{
|
|
||||||
bins: bins,
|
|
||||||
signer: signer,
|
|
||||||
loginServerURL: loginServer,
|
|
||||||
cs: cs,
|
|
||||||
}
|
|
||||||
|
|
||||||
h.makeTestNode(t, bins, loginServer)
|
|
||||||
|
|
||||||
t.Run("do", func(t *testing.T) {
|
t.Run("do", func(t *testing.T) {
|
||||||
for n, distro := range distros {
|
for n, distro := range distros {
|
||||||
@ -344,6 +258,7 @@ func TestVMIntegrationEndToEnd(t *testing.T) {
|
|||||||
|
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
|
h := newHarness(t)
|
||||||
dir := t.TempDir()
|
dir := t.TempDir()
|
||||||
|
|
||||||
err := ramsem.Acquire(ctx, int64(distro.mem))
|
err := ramsem.Acquire(ctx, int64(distro.mem))
|
||||||
@ -352,7 +267,7 @@ func TestVMIntegrationEndToEnd(t *testing.T) {
|
|||||||
}
|
}
|
||||||
defer ramsem.Release(int64(distro.mem))
|
defer ramsem.Release(int64(distro.mem))
|
||||||
|
|
||||||
h.mkVM(t, n, distro, string(pubkey), loginServer, dir)
|
h.mkVM(t, n, distro, h.pubKey, h.loginServerURL, dir)
|
||||||
var ipm ipMapping
|
var ipm ipMapping
|
||||||
|
|
||||||
t.Run("wait-for-start", func(t *testing.T) {
|
t.Run("wait-for-start", func(t *testing.T) {
|
||||||
@ -361,12 +276,12 @@ func TestVMIntegrationEndToEnd(t *testing.T) {
|
|||||||
var ok bool
|
var ok bool
|
||||||
for {
|
for {
|
||||||
<-waiter.C
|
<-waiter.C
|
||||||
ipMu.Lock()
|
h.ipMu.Lock()
|
||||||
if ipm, ok = ipMap[distro.name]; ok {
|
if ipm, ok = h.ipMap[distro.name]; ok {
|
||||||
ipMu.Unlock()
|
h.ipMu.Unlock()
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
ipMu.Unlock()
|
h.ipMu.Unlock()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -376,7 +291,7 @@ func TestVMIntegrationEndToEnd(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h Harness) testDistro(t *testing.T, d Distro, ipm ipMapping) {
|
func (h *Harness) testDistro(t *testing.T, d Distro, ipm ipMapping) {
|
||||||
signer := h.signer
|
signer := h.signer
|
||||||
loginServer := h.loginServerURL
|
loginServer := h.loginServerURL
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user