diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index f4969033..583cf22e 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -57,7 +57,7 @@ jobs: - name: Test run: | - sudo -s -u ${USER} bash -c 'make test' + sudo -s -u ${USER} bash -c 'make test-linux' - uses: actions/upload-artifact@v4 if: always() diff --git a/Makefile b/Makefile index 5df2f8bc..ea0ca41d 100644 --- a/Makefile +++ b/Makefile @@ -67,6 +67,10 @@ cross: $(TOOLS_BINDIR)/makefat test-companion: GOOS=linux go build -ldflags "$(LDFLAGS)" -o bin/test-companion ./cmd/test-companion -.PHONY: test -test: gvproxy test-companion - go test -timeout 20m -v ./... +.PHONY: test-linux +test-linux: gvproxy test-companion + go test -timeout 20m -v ./test-qemu + +.PHONY: test-mac +test-mac: gvproxy + go test -timeout 20m -v ./test-vfkit diff --git a/test-qemu/basic_test.go b/test-qemu/basic_test.go new file mode 100644 index 00000000..e30e0c50 --- /dev/null +++ b/test-qemu/basic_test.go @@ -0,0 +1,45 @@ +package e2eqemu + +import ( + "github.com/containers/gvisor-tap-vsock/pkg/types" + e2e "github.com/containers/gvisor-tap-vsock/test" + "github.com/onsi/ginkgo" + "github.com/onsi/gomega" +) + +var _ = ginkgo.Describe("connectivity with qemu", func() { + e2e.BasicConnectivityTests(e2e.BasicTestProps{ + SSHExec: sshExec, + }) +}) + +var _ = ginkgo.Describe("dns with qemu", func() { + e2e.BasicDNSTests(e2e.BasicTestProps{ + SSHExec: sshExec, + Sock: sock, + }) +}) + +var _ = ginkgo.Describe("command-line format", func() { + ginkgo.It("should convert Command to command line format", func() { + command := types.NewGvproxyCommand() + command.AddEndpoint("unix:///tmp/network.sock") + command.Debug = true + command.AddQemuSocket("tcp://0.0.0.0:1234") + command.PidFile = "~/gv-pidfile.txt" + command.LogFile = "~/gv.log" + command.AddForwardUser("demouser") + + cmd := command.ToCmdline() + gomega.Expect(cmd).To(gomega.Equal([]string{ + "-listen", "unix:///tmp/network.sock", + "-debug", + "-mtu", "1500", + "-ssh-port", "2222", + "-listen-qemu", "tcp://0.0.0.0:1234", + "-forward-user", "demouser", + "-pid-file", "~/gv-pidfile.txt", + "-log-file", "~/gv.log", + })) + }) +}) diff --git a/test/efi_darwin_arm64_test.go b/test-qemu/efi_darwin_arm64_test.go similarity index 98% rename from test/efi_darwin_arm64_test.go rename to test-qemu/efi_darwin_arm64_test.go index affc35bb..539acae8 100644 --- a/test/efi_darwin_arm64_test.go +++ b/test-qemu/efi_darwin_arm64_test.go @@ -1,4 +1,4 @@ -package e2e +package e2eqemu import ( "fmt" diff --git a/test/efi_generic.go b/test-qemu/efi_generic.go similarity index 83% rename from test/efi_generic.go rename to test-qemu/efi_generic.go index c1106595..a5899ef5 100644 --- a/test/efi_generic.go +++ b/test-qemu/efi_generic.go @@ -1,6 +1,6 @@ //go:build !(darwin && arm64) -package e2e +package e2eqemu func efiArgs() (string, error) { return "", nil diff --git a/test/port_forwarding_test.go b/test-qemu/port_forwarding_test.go similarity index 99% rename from test/port_forwarding_test.go rename to test-qemu/port_forwarding_test.go index 2e32e8f1..d08951c6 100644 --- a/test/port_forwarding_test.go +++ b/test-qemu/port_forwarding_test.go @@ -1,4 +1,4 @@ -package e2e +package e2eqemu import ( "context" diff --git a/test/suite_test.go b/test-qemu/suite_test.go similarity index 89% rename from test/suite_test.go rename to test-qemu/suite_test.go index 67534dbb..b25ddfa9 100644 --- a/test/suite_test.go +++ b/test-qemu/suite_test.go @@ -1,4 +1,4 @@ -package e2e +package e2eqemu import ( "flag" @@ -13,9 +13,10 @@ import ( "testing" "time" + e2e_utils "github.com/containers/gvisor-tap-vsock/test-utils" + "github.com/onsi/ginkgo" "github.com/onsi/gomega" - "github.com/pkg/errors" log "github.com/sirupsen/logrus" ) @@ -63,15 +64,15 @@ func init() { var _ = ginkgo.BeforeSuite(func() { gomega.Expect(os.MkdirAll(filepath.Join(tmpDir, "disks"), os.ModePerm)).Should(gomega.Succeed()) - downloader, err := NewFcosDownloader(filepath.Join(tmpDir, "disks")) + downloader, err := e2e_utils.NewFcosDownloader(filepath.Join(tmpDir, "disks")) gomega.Expect(err).ShouldNot(gomega.HaveOccurred()) - qemuImage, err := downloader.DownloadImage() + qemuImage, err := downloader.DownloadImage("qemu", "qcow2.xz") gomega.Expect(err).ShouldNot(gomega.HaveOccurred()) - publicKey, err := createSSHKeys() + publicKey, err := e2e_utils.CreateSSHKeys(publicKeyFile, privateKeyFile) gomega.Expect(err).ShouldNot(gomega.HaveOccurred()) - err = CreateIgnition(ignFile, publicKey, ignitionUser, ignitionPasswordHash) + err = e2e_utils.CreateIgnition(ignFile, publicKey, ignitionUser, ignitionPasswordHash) gomega.Expect(err).ShouldNot(gomega.HaveOccurred()) outer: @@ -154,7 +155,7 @@ outer: }) func qemuExecutable() string { - qemuBinaries := []string{"qemu-kvm", fmt.Sprintf("qemu-system-%s", coreosArch())} + qemuBinaries := []string{"qemu-kvm", fmt.Sprintf("qemu-system-%s", e2e_utils.CoreosArch())} for _, binary := range qemuBinaries { path, err := exec.LookPath(binary) if err == nil && path != "" { @@ -182,26 +183,6 @@ func qemuArgs() string { return fmt.Sprintf("-machine %s,accel=%s:tcg -smp 4 -cpu host ", machine, accel) } -func createSSHKeys() (string, error) { - _ = os.Remove(publicKeyFile) - _ = os.Remove(privateKeyFile) - err := exec.Command("ssh-keygen", "-N", "", "-t", "ed25519", "-f", privateKeyFile).Run() - if err != nil { - return "", errors.Wrap(err, "Could not generate ssh keys") - } - - return readPublicKey() -} - -func readPublicKey() (string, error) { - publicKey, err := os.ReadFile(publicKeyFile) - if err != nil { - return "", nil - } - - return strings.TrimSpace(string(publicKey)), nil -} - func scp(src, dst string) error { sshCmd := exec.Command("scp", "-o", "UserKnownHostsFile=/dev/null", diff --git a/test/fcos.go b/test-utils/fcos.go similarity index 83% rename from test/fcos.go rename to test-utils/fcos.go index 97df8c63..c8bbbd6c 100644 --- a/test/fcos.go +++ b/test-utils/fcos.go @@ -1,4 +1,4 @@ -package e2e +package e2eutils import ( "os" @@ -27,6 +27,11 @@ type fcosDownloadInfo struct { Sha256Sum string } +type ArtifactFormat struct { + Artifact string + Format string +} + func NewFcosDownloader(dataDir string) (*FcosDownload, error) { return &FcosDownload{ DataDir: dataDir, @@ -38,14 +43,13 @@ func imageName(info *fcosDownloadInfo) string { return urlSplit[len(urlSplit)-1] } -func (downloader *FcosDownload) DownloadImage() (string, error) { - info, err := getFCOSDownload() +func (downloader *FcosDownload) DownloadImage(artifactType string, formatType string) (string, error) { + info, err := getFCOSDownload(artifactType, formatType) if err != nil { return "", err } compressedImage := filepath.Join(downloader.DataDir, imageName(info)) - uncompressedImage := strings.TrimSuffix(filepath.Join(filepath.Dir(compressedImage), imageName(info)), ".xz") // check if the latest image is already present ok, err := downloader.updateAvailable(info, compressedImage) @@ -58,10 +62,8 @@ func (downloader *FcosDownload) DownloadImage() (string, error) { } } - if _, err := os.Stat(uncompressedImage); err == nil { - return uncompressedImage, nil - } - if err := Decompress(compressedImage, uncompressedImage); err != nil { + uncompressedImage := "" + if uncompressedImage, err = Decompress(compressedImage); err != nil { return "", err } return uncompressedImage, nil @@ -91,7 +93,7 @@ func (downloader *FcosDownload) updateAvailable(info *fcosDownloadInfo, compress // as of 2024-05-28, these are the 4 architectures available in // curl https://builds.coreos.fedoraproject.org/streams/next.json -func coreosArch() string { +func CoreosArch() string { switch runtime.GOARCH { case "amd64": return "x86_64" @@ -107,7 +109,7 @@ func coreosArch() string { // This should get Exported and stay put as it will apply to all fcos downloads // getFCOS parses fedoraCoreOS's stream and returns the image download URL and the release version -func getFCOSDownload() (*fcosDownloadInfo, error) { +func getFCOSDownload(artifactType string, formatType string) (*fcosDownloadInfo, error) { streamurl := fedoracoreos.GetStreamURL(fedoracoreos.StreamNext) resp, err := http.Get(streamurl.String()) if err != nil { @@ -127,7 +129,7 @@ func getFCOSDownload() (*fcosDownloadInfo, error) { if err := json.Unmarshal(body, &fcosstable); err != nil { return nil, err } - arch, ok := fcosstable.Architectures[coreosArch()] + arch, ok := fcosstable.Architectures[CoreosArch()] if !ok { return nil, fmt.Errorf("unable to pull VM image: no targetArch in stream") } @@ -135,19 +137,19 @@ func getFCOSDownload() (*fcosDownloadInfo, error) { if artifacts == nil { return nil, fmt.Errorf("unable to pull VM image: no artifact in stream") } - qemu, ok := artifacts["qemu"] + artifact, ok := artifacts[artifactType] if !ok { return nil, fmt.Errorf("unable to pull VM image: no qemu artifact in stream") } - formats := qemu.Formats + formats := artifact.Formats if formats == nil { return nil, fmt.Errorf("unable to pull VM image: no formats in stream") } - qcow, ok := formats["qcow2.xz"] + format, ok := formats[formatType] if !ok { return nil, fmt.Errorf("unable to pull VM image: no qcow2.xz format in stream") } - disk := qcow.Disk + disk := format.Disk if disk == nil { return nil, fmt.Errorf("unable to pull VM image: no disk in stream") } diff --git a/test/ignition.go b/test-utils/ignition.go similarity index 99% rename from test/ignition.go rename to test-utils/ignition.go index a9dcbff1..4c7aae05 100644 --- a/test/ignition.go +++ b/test-utils/ignition.go @@ -1,4 +1,4 @@ -package e2e +package e2eutils import ( "encoding/json" diff --git a/test/ignition_schema.go b/test-utils/ignition_schema.go similarity index 99% rename from test/ignition_schema.go rename to test-utils/ignition_schema.go index 5fb9d751..e0dfe86f 100644 --- a/test/ignition_schema.go +++ b/test-utils/ignition_schema.go @@ -1,4 +1,4 @@ -package e2e +package e2eutils // Taken from https://github.com/coreos/ignition/blob/master/config/v3_2/types/schema.go diff --git a/test-utils/port.go b/test-utils/port.go new file mode 100644 index 00000000..3c34fe13 --- /dev/null +++ b/test-utils/port.go @@ -0,0 +1,19 @@ +package e2eutils + +import ( + "net" + "strconv" +) + +func IsPortAvailable(port int) bool { + return IsHostPortAvailable("127.0.0.1", port) +} + +func IsHostPortAvailable(host string, port int) bool { + listener, err := net.Listen("tcp", net.JoinHostPort(host, strconv.Itoa(port))) + if err != nil { + return false + } + listener.Close() + return true +} diff --git a/test/pull.go b/test-utils/pull.go similarity index 55% rename from test/pull.go rename to test-utils/pull.go index f1fa2360..819c052e 100644 --- a/test/pull.go +++ b/test-utils/pull.go @@ -1,6 +1,7 @@ -package e2e +package e2eutils import ( + "compress/gzip" "fmt" "io" "net/http" @@ -48,18 +49,37 @@ func DownloadVMImage(downloadURL string, localImagePath string) error { return nil } -func Decompress(localPath, uncompressedPath string) error { +func Decompress(localPath string) (string, error) { + uncompressedPath := "" + if strings.HasSuffix(localPath, ".xz") { + uncompressedPath = strings.TrimSuffix(localPath, ".xz") + } else if strings.HasSuffix(localPath, ".gz") { + uncompressedPath = strings.TrimSuffix(localPath, ".gz") + } + + if uncompressedPath == "" { + return "", fmt.Errorf("unsupported compression for %s", localPath) + } + + // we remove the uncompressed file if already exists. Maybe it has been used earlier and can affect the tests result + os.Remove(uncompressedPath) + uncompressedFileWriter, err := os.OpenFile(uncompressedPath, os.O_CREATE|os.O_RDWR, 0600) if err != nil { - return err + return "", err } - if !strings.HasSuffix(localPath, ".xz") { - return fmt.Errorf("unsupported compression for %s", localPath) + fmt.Printf("Extracting %s\n", localPath) + if strings.HasSuffix(localPath, ".xz") { + err = decompressXZ(localPath, uncompressedFileWriter) + } else { + err = decompressGZ(localPath, uncompressedFileWriter) } - fmt.Printf("Extracting %s\n", localPath) - return decompressXZ(localPath, uncompressedFileWriter) + if err != nil { + return "", err + } + return uncompressedPath, nil } // Will error out if file without .xz already exists @@ -79,3 +99,30 @@ func decompressXZ(src string, output io.Writer) error { }() return cmd.Run() } + +func decompressGZ(src string, output io.Writer) error { + file, err := os.Open(src) + if err != nil { + return err + } + defer file.Close() + + // Create a gzip reader + reader, err := gzip.NewReader(file) + if err != nil { + return err + } + defer reader.Close() + + for { + _, err := io.CopyN(output, reader, 1024) + if err != nil { + if err == io.EOF { + break + } + return err + } + } + + return nil +} diff --git a/test-utils/ssh.go b/test-utils/ssh.go new file mode 100644 index 00000000..51cf00bc --- /dev/null +++ b/test-utils/ssh.go @@ -0,0 +1,29 @@ +package e2eutils + +import ( + "os" + "os/exec" + "strings" + + "github.com/pkg/errors" +) + +func CreateSSHKeys(publicKeyFile, privateKeyFile string) (string, error) { + _ = os.Remove(publicKeyFile) + _ = os.Remove(privateKeyFile) + err := exec.Command("ssh-keygen", "-N", "", "-t", "ed25519", "-f", privateKeyFile).Run() + if err != nil { + return "", errors.Wrap(err, "Could not generate ssh keys") + } + + return readPublicKey(publicKeyFile) +} + +func readPublicKey(publicKeyFile string) (string, error) { + publicKey, err := os.ReadFile(publicKeyFile) + if err != nil { + return "", nil + } + + return strings.TrimSpace(string(publicKey)), nil +} diff --git a/test-vfkit/basic_test.go b/test-vfkit/basic_test.go new file mode 100644 index 00000000..b46925b1 --- /dev/null +++ b/test-vfkit/basic_test.go @@ -0,0 +1,19 @@ +package e2evfkit + +import ( + e2e "github.com/containers/gvisor-tap-vsock/test" + "github.com/onsi/ginkgo" +) + +var _ = ginkgo.Describe("connectivity with vfkit", func() { + e2e.BasicConnectivityTests(e2e.BasicTestProps{ + SSHExec: sshExec, + }) +}) + +var _ = ginkgo.Describe("dns with vfkit", func() { + e2e.BasicDNSTests(e2e.BasicTestProps{ + SSHExec: sshExec, + Sock: sock, + }) +}) diff --git a/test-vfkit/vfkit_suite_test.go b/test-vfkit/vfkit_suite_test.go new file mode 100644 index 00000000..8c9a589e --- /dev/null +++ b/test-vfkit/vfkit_suite_test.go @@ -0,0 +1,219 @@ +package e2evfkit + +import ( + "flag" + "fmt" + "os" + "os/exec" + "path/filepath" + "strconv" + "strings" + "testing" + "time" + + e2e_utils "github.com/containers/gvisor-tap-vsock/test-utils" + + "github.com/onsi/ginkgo" + "github.com/onsi/gomega" + log "github.com/sirupsen/logrus" + + "golang.org/x/mod/semver" +) + +func TestSuite(t *testing.T) { + gomega.RegisterFailHandler(ginkgo.Fail) + ginkgo.RunSpecs(t, "gvisor-tap-vsock suite") +} + +const ( + sock = "/tmp/gvproxy-api-vfkit.sock" + vfkitSock = "/tmp/vfkit.sock" + sshPort = 2223 + ignitionUser = "test" + // #nosec "test" (for manual usage) + ignitionPasswordHash = "$y$j9T$TqJWt3/mKJbH0sYi6B/LD1$QjVRuUgntjTHjAdAkqhkr4F73m.Be4jBXdAaKw98sPC" // notsecret + efiStore = "efi-variable-store" + vfkitVersionNeeded = 0.6 +) + +var ( + tmpDir string + binDir string + host *exec.Cmd + client *exec.Cmd + privateKeyFile string + publicKeyFile string + ignFile string +) + +func init() { + flag.StringVar(&tmpDir, "tmpDir", "../tmp", "temporary working directory") + flag.StringVar(&binDir, "bin", "../bin", "directory with compiled binaries") + privateKeyFile = filepath.Join(tmpDir, "id_test") + publicKeyFile = privateKeyFile + ".pub" + ignFile = filepath.Join(tmpDir, "test.ign") +} + +var _ = ginkgo.BeforeSuite(func() { + // clear the environment before running the tests. It may happen the tests were abruptly stopped earlier leaving a dirty env + clear() + + // check if vfkit version is greater than v0.5 (ignition support is available starting from v0.6) + version, err := vfkitVersion() + gomega.Expect(err).ShouldNot(gomega.HaveOccurred()) + gomega.Expect(version >= vfkitVersionNeeded).Should(gomega.BeTrue()) + + // check if ssh port is free + gomega.Expect(e2e_utils.IsPortAvailable(sshPort)).Should(gomega.BeTrue()) + + gomega.Expect(os.MkdirAll(filepath.Join(tmpDir, "disks"), os.ModePerm)).Should(gomega.Succeed()) + + downloader, err := e2e_utils.NewFcosDownloader(filepath.Join(tmpDir, "disks")) + gomega.Expect(err).ShouldNot(gomega.HaveOccurred()) + fcosImage, err := downloader.DownloadImage("applehv", "raw.gz") + gomega.Expect(err).ShouldNot(gomega.HaveOccurred()) + + publicKey, err := e2e_utils.CreateSSHKeys(publicKeyFile, privateKeyFile) + gomega.Expect(err).ShouldNot(gomega.HaveOccurred()) + + err = e2e_utils.CreateIgnition(ignFile, publicKey, ignitionUser, ignitionPasswordHash) + gomega.Expect(err).ShouldNot(gomega.HaveOccurred()) + + errors := make(chan error) + +outer: + for panics := 0; ; panics++ { + _ = os.Remove(sock) + + // #nosec + host = exec.Command(filepath.Join(binDir, "gvproxy"), fmt.Sprintf("--ssh-port=%d", sshPort), fmt.Sprintf("--listen=unix://%s", sock), fmt.Sprintf("--listen-vfkit=unixgram://%s", vfkitSock)) + + host.Stderr = os.Stderr + host.Stdout = os.Stdout + gomega.Expect(host.Start()).Should(gomega.Succeed()) + go func() { + if err := host.Wait(); err != nil { + log.Error(err) + errors <- err + } + }() + + for { + _, err := os.Stat(sock) + if os.IsNotExist(err) { + log.Info("waiting for socket") + time.Sleep(100 * time.Millisecond) + continue + } + _, err = os.Stat(vfkitSock) + if os.IsNotExist(err) { + log.Info("waiting for vfkit socket") + time.Sleep(100 * time.Millisecond) + continue + } + break + } + + vfkitArgs := `--cpus 2 --memory 2048 --bootloader efi,variable-store=%s,create --device virtio-blk,path=%s --ignition %s --device virtio-net,unixSocketPath=%s,mac=5a:94:ef:e4:0c:ee` + // #nosec + client = exec.Command(vfkitExecutable(), strings.Split(fmt.Sprintf(vfkitArgs, efiStore, fcosImage, ignFile, vfkitSock), " ")...) + client.Stderr = os.Stderr + client.Stdout = os.Stdout + gomega.Expect(client.Start()).Should(gomega.Succeed()) + go func() { + if err := client.Wait(); err != nil { + log.Error(err) + errors <- err + } + }() + + for { + _, err := sshExec("whoami") + if err == nil { + break outer + } + + select { + case err := <-errors: + log.Errorf("Error %v", err) + // this expect will always fail so the tests stop + gomega.Expect(err).To(gomega.Equal(nil)) + break outer + case <-time.After(1 * time.Second): + log.Infof("waiting for client to connect: %v", err) + } + } + } + + time.Sleep(5 * time.Second) +}) + +func vfkitVersion() (float64, error) { + executable := vfkitExecutable() + if executable == "" { + return 0, fmt.Errorf("vfkit executable not found") + } + out, err := exec.Command(executable, "-v").Output() + if err != nil { + return 0, err + } + version := strings.TrimPrefix(string(out), "vfkit version:") + majorMinor := strings.TrimPrefix(semver.MajorMinor(strings.TrimSpace(version)), "v") + versionF, err := strconv.ParseFloat(majorMinor, 64) + if err != nil { + return 0, err + } + return versionF, nil +} + +func vfkitExecutable() string { + vfkitBinaries := []string{"vfkit"} + for _, binary := range vfkitBinaries { + path, err := exec.LookPath(binary) + if err == nil && path != "" { + return path + } + } + + return "" +} + +func sshExec(cmd ...string) ([]byte, error) { + return sshCommand(cmd...).Output() +} + +func sshCommand(cmd ...string) *exec.Cmd { + sshCmd := exec.Command("ssh", + "-o", "UserKnownHostsFile=/dev/null", + "-o", "StrictHostKeyChecking=no", + "-o", "IdentitiesOnly=yes", + "-i", privateKeyFile, + "-p", strconv.Itoa(sshPort), + fmt.Sprintf("%s@127.0.0.1", ignitionUser), "--", strings.Join(cmd, " ")) // #nosec G204 + return sshCmd +} + +func clear() { + _ = os.Remove(efiStore) + _ = os.Remove(sock) + _ = os.Remove(vfkitSock) + + // this should be handled by vfkit once https://github.com/crc-org/vfkit/pull/230 gets merged + // it removes the ignition.sock file + socketPath := filepath.Join(os.TempDir(), "ignition.sock") + _ = os.Remove(socketPath) +} + +var _ = ginkgo.AfterSuite(func() { + if host != nil { + if err := host.Process.Kill(); err != nil { + log.Error(err) + } + } + if client != nil { + if err := client.Process.Kill(); err != nil { + log.Error(err) + } + } + clear() +}) diff --git a/test/basic_test.go b/test/basic_tests.go similarity index 75% rename from test/basic_test.go rename to test/basic_tests.go index 4b9d354c..fec6d277 100644 --- a/test/basic_test.go +++ b/test/basic_tests.go @@ -6,14 +6,20 @@ import ( "net/http" gvproxyclient "github.com/containers/gvisor-tap-vsock/pkg/client" + "github.com/containers/gvisor-tap-vsock/pkg/types" "github.com/onsi/ginkgo" "github.com/onsi/gomega" ) -var _ = ginkgo.Describe("connectivity", func() { +type BasicTestProps struct { + SSHExec func(cmd ...string) ([]byte, error) + Sock string +} + +func BasicConnectivityTests(props BasicTestProps) { ginkgo.It("should configure the interface", func() { - out, err := sshExec("ifconfig $(route | grep '^default' | grep -o '[^ ]*$')") + out, err := props.SSHExec("ifconfig $(route | grep '^default' | grep -o '[^ ]*$')") gomega.Expect(err).ShouldNot(gomega.HaveOccurred()) gomega.Expect(string(out)).To(gomega.ContainSubstring("mtu 1500")) gomega.Expect(string(out)).To(gomega.ContainSubstring("inet 192.168.127.2")) @@ -22,72 +28,72 @@ var _ = ginkgo.Describe("connectivity", func() { }) ginkgo.It("should configure the default route", func() { - out, err := sshExec("ip route show") + out, err := props.SSHExec("ip route show") gomega.Expect(err).ShouldNot(gomega.HaveOccurred()) gomega.Expect(string(out)).To(gomega.MatchRegexp(`default via 192\.168\.127\.1 dev (.*?) proto dhcp (src 192\.168\.127\.2 )?metric 100`)) }) ginkgo.It("should configure dns settings", func() { - out, err := sshExec("cat /etc/resolv.conf") + out, err := props.SSHExec("cat /etc/resolv.conf") gomega.Expect(err).ShouldNot(gomega.HaveOccurred()) gomega.Expect(string(out)).To(gomega.ContainSubstring("nameserver 192.168.127.1")) }) ginkgo.It("should ping the tap device", func() { - out, err := sshExec("ping -c2 192.168.127.2") + out, err := props.SSHExec("ping -c2 192.168.127.2") gomega.Expect(err).ShouldNot(gomega.HaveOccurred()) gomega.Expect(string(out)).To(gomega.ContainSubstring("2 packets transmitted, 2 received, 0% packet loss")) }) ginkgo.It("should ping the gateway", func() { - out, err := sshExec("ping -c2 192.168.127.1") + out, err := props.SSHExec("ping -c2 192.168.127.1") gomega.Expect(err).ShouldNot(gomega.HaveOccurred()) gomega.Expect(string(out)).To(gomega.ContainSubstring("2 packets transmitted, 2 received, 0% packet loss")) }) -}) +} -var _ = ginkgo.Describe("dns", func() { +func BasicDNSTests(props BasicTestProps) { ginkgo.It("should resolve redhat.com", func() { - out, err := sshExec("nslookup redhat.com") + out, err := props.SSHExec("nslookup redhat.com") gomega.Expect(err).ShouldNot(gomega.HaveOccurred()) gomega.Expect(string(out)).To(gomega.ContainSubstring("Address: 52.200.142.250")) }) ginkgo.It("should resolve CNAME record for docs.crc.dev", func() { - out, err := sshExec("nslookup -query=cname docs.crc.dev") + out, err := props.SSHExec("nslookup -query=cname docs.crc.dev") gomega.Expect(err).ShouldNot(gomega.HaveOccurred()) gomega.Expect(string(out)).To(gomega.ContainSubstring("docs.crc.dev canonical name = webredir.gandi.net.")) }) ginkgo.It("should resolve MX record for crc.dev", func() { - out, err := sshExec("nslookup -query=mx crc.dev") + out, err := props.SSHExec("nslookup -query=mx crc.dev") gomega.Expect(err).ShouldNot(gomega.HaveOccurred()) gomega.Expect(string(out)).To(gomega.ContainSubstring("crc.dev mail exchanger = 10 spool.mail.gandi.net.")) }) ginkgo.It("should resolve NS record for wikipedia.org", func() { - out, err := sshExec("nslookup -query=ns wikipedia.org") + out, err := props.SSHExec("nslookup -query=ns wikipedia.org") gomega.Expect(err).ShouldNot(gomega.HaveOccurred()) gomega.Expect(string(out)).To(gomega.ContainSubstring("wikipedia.org nameserver = ns0.wikimedia.org.")) }) ginkgo.It("should resolve IMAPS SRV record for crc.dev", func() { - out, err := sshExec("nslookup -query=srv _imaps._tcp.crc.dev") + out, err := props.SSHExec("nslookup -query=srv _imaps._tcp.crc.dev") gomega.Expect(err).ShouldNot(gomega.HaveOccurred()) gomega.Expect(string(out)).To(gomega.ContainSubstring(`_imaps._tcp.crc.dev service = 0 1 993 mail.gandi.net.`)) }) ginkgo.It("should resolve TXT for crc.dev", func() { - out, err := sshExec("nslookup -query=txt crc.dev") + out, err := props.SSHExec("nslookup -query=txt crc.dev") gomega.Expect(err).ShouldNot(gomega.HaveOccurred()) gomega.Expect(string(out)).To(gomega.ContainSubstring(`text = "v=spf1`)) }) ginkgo.It("should resolve gateway.containers.internal", func() { - out, err := sshExec("nslookup gateway.containers.internal") + out, err := props.SSHExec("nslookup gateway.containers.internal") gomega.Expect(err).ShouldNot(gomega.HaveOccurred()) gomega.Expect(string(out)).To(gomega.ContainSubstring("Address: 192.168.127.1")) }) ginkgo.It("should resolve host.containers.internal", func() { - out, err := sshExec("nslookup host.containers.internal") + out, err := props.SSHExec("nslookup host.containers.internal") gomega.Expect(err).ShouldNot(gomega.HaveOccurred()) gomega.Expect(string(out)).To(gomega.ContainSubstring("Address: 192.168.127.254")) }) @@ -96,7 +102,7 @@ var _ = ginkgo.Describe("dns", func() { client := gvproxyclient.New(&http.Client{ Transport: &http.Transport{ DialContext: func(_ context.Context, _, _ string) (net.Conn, error) { - return net.Dial("unix", sock) + return net.Dial("unix", props.Sock) }, }, }, "http://base") @@ -111,7 +117,7 @@ var _ = ginkgo.Describe("dns", func() { }) gomega.Expect(err).ShouldNot(gomega.HaveOccurred()) - out, err := sshExec("nslookup test.dynamic.internal") + out, err := props.SSHExec("nslookup test.dynamic.internal") gomega.Expect(err).ShouldNot(gomega.HaveOccurred()) gomega.Expect(string(out)).To(gomega.ContainSubstring("Address: 192.168.127.254")) @@ -121,7 +127,7 @@ var _ = ginkgo.Describe("dns", func() { client := gvproxyclient.New(&http.Client{ Transport: &http.Transport{ DialContext: func(_ context.Context, _, _ string) (net.Conn, error) { - return net.Dial("unix", sock) + return net.Dial("unix", props.Sock) }, }, }, "http://base") @@ -147,7 +153,7 @@ var _ = ginkgo.Describe("dns", func() { }) gomega.Expect(err).ShouldNot(gomega.HaveOccurred()) - out, err := sshExec("nslookup test.dynamic.internal") + out, err := props.SSHExec("nslookup test.dynamic.internal") gomega.Expect(err).ShouldNot(gomega.HaveOccurred()) gomega.Expect(string(out)).To(gomega.ContainSubstring("Address: 192.168.127.253")) @@ -157,7 +163,7 @@ var _ = ginkgo.Describe("dns", func() { client := gvproxyclient.New(&http.Client{ Transport: &http.Transport{ DialContext: func(_ context.Context, _, _ string) (net.Conn, error) { - return net.Dial("unix", sock) + return net.Dial("unix", props.Sock) }, }, }, "http://base") @@ -174,7 +180,7 @@ var _ = ginkgo.Describe("dns", func() { }, }, }) - out, err := sshExec("nslookup test.dynamic.internal") + out, err := props.SSHExec("nslookup test.dynamic.internal") gomega.Expect(err).ShouldNot(gomega.HaveOccurred()) gomega.Expect(string(out)).To(gomega.ContainSubstring("Address: 192.168.127.2")) @@ -187,36 +193,12 @@ var _ = ginkgo.Describe("dns", func() { }, }, }) - out, err = sshExec("nslookup *.dynamic.testing") + out, err = props.SSHExec("nslookup *.dynamic.testing") gomega.Expect(err).ShouldNot(gomega.HaveOccurred()) gomega.Expect(string(out)).To(gomega.ContainSubstring("Address: 192.168.127.2")) - out, err = sshExec("nslookup gateway.testing") + out, err = props.SSHExec("nslookup gateway.testing") gomega.Expect(err).ShouldNot(gomega.HaveOccurred()) gomega.Expect(string(out)).To(gomega.ContainSubstring("Address: 192.168.127.1")) }) -}) - -var _ = ginkgo.Describe("command-line format", func() { - ginkgo.It("should convert Command to command line format", func() { - command := types.NewGvproxyCommand() - command.AddEndpoint("unix:///tmp/network.sock") - command.Debug = true - command.AddQemuSocket("tcp://0.0.0.0:1234") - command.PidFile = "~/gv-pidfile.txt" - command.LogFile = "~/gv.log" - command.AddForwardUser("demouser") - - cmd := command.ToCmdline() - gomega.Expect(cmd).To(gomega.Equal([]string{ - "-listen", "unix:///tmp/network.sock", - "-debug", - "-mtu", "1500", - "-ssh-port", "2222", - "-listen-qemu", "tcp://0.0.0.0:1234", - "-forward-user", "demouser", - "-pid-file", "~/gv-pidfile.txt", - "-log-file", "~/gv.log", - })) - }) -}) +}