Skip to content

Commit

Permalink
test: move test utils files to the test-utils folder
Browse files Browse the repository at this point in the history
Tests that are currently on the main branch only runs against a qemu VM. We have other use cases that needs to be tested like running against a vfkit VM.
This commit reorganizes the tests code a bit by moving the files that can be shared to support different implementation in their own folder.
The reasoning behind this is that every hypervisor should have its own beforeSuite func to download/run a specific VM image. By moving the utils files we can reuse the same code.

For the same reason the code targeting qemu is moved to the test-qemu folder. By doing so, we can run the tests within the test-qemu folder on the ubuntu workflow and, in future, when the nested virt will be enabled on github runners, the vfkit tests on macOS.

Signed-off-by: Luca Stocchi <[email protected]>
  • Loading branch information
lstocchi committed Nov 29, 2024
1 parent e694d60 commit 25e8298
Show file tree
Hide file tree
Showing 13 changed files with 225 additions and 106 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,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()
Expand Down
6 changes: 3 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,6 @@ 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
45 changes: 45 additions & 0 deletions test-qemu/basic_test.go
Original file line number Diff line number Diff line change
@@ -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",
}))
})
})
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package e2e
package e2eqemu

import (
"fmt"
Expand Down
2 changes: 1 addition & 1 deletion test/efi_generic.go → test-qemu/efi_generic.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//go:build !(darwin && arm64)

package e2e
package e2eqemu

func efiArgs() (string, error) {
return "", nil
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package e2e
package e2eqemu

import (
"context"
Expand Down Expand Up @@ -59,6 +59,36 @@ var _ = ginkgo.Describe("port forwarding", func() {
gomega.Expect(string(out)).To(gomega.ContainSubstring("Hello from the host"))
})

ginkgo.It("should reach a http server on the host", func() {
ln, err := net.Listen("tcp", "127.0.0.1:9090")
gomega.Expect(err).ShouldNot(gomega.HaveOccurred())
defer ln.Close()

mux := http.NewServeMux()
mux.HandleFunc("/", func(writer http.ResponseWriter, _ *http.Request) {
_, _ = writer.Write([]byte("Hello from the host"))
})
go func() {
s := &http.Server{
Handler: mux,
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
}
err := s.Serve(ln)
if err != nil {
log.Error(err)
}
}()

out, err := sshExec("curl http://host.containers.internal:9090")
gomega.Expect(err).ShouldNot(gomega.HaveOccurred())
gomega.Expect(string(out)).To(gomega.ContainSubstring("Hello from the host"))

out, err = sshExec("curl http://host.docker.internal:9090")
gomega.Expect(err).ShouldNot(gomega.HaveOccurred())
gomega.Expect(string(out)).To(gomega.ContainSubstring("Hello from the host"))
})

ginkgo.It("should reach a http server in the VM using dynamic port forwarding", func() {
_, err := net.Dial("tcp", "127.0.0.1:9090")
gomega.Expect(err).Should(gomega.HaveOccurred())
Expand Down
35 changes: 8 additions & 27 deletions test/suite_test.go → test-qemu/suite_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package e2e
package e2eqemu

import (
"flag"
Expand All @@ -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"
)

Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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 != "" {
Expand Down Expand Up @@ -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",
Expand Down
33 changes: 19 additions & 14 deletions test/fcos.go → test-utils/fcos.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package e2e
package e2eutils

import (
"os"
Expand Down Expand Up @@ -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,
Expand All @@ -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)
Expand All @@ -58,12 +62,13 @@ func (downloader *FcosDownload) DownloadImage() (string, error) {
}
}

uncompressedImage := ""
if uncompressedImage, err = Decompress(compressedImage); err != nil {
return "", err
}
if _, err := os.Stat(uncompressedImage); err == nil {
return uncompressedImage, nil
}
if err := Decompress(compressedImage, uncompressedImage); err != nil {
return "", err
}
return uncompressedImage, nil
}

Expand Down Expand Up @@ -91,7 +96,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"
Expand All @@ -107,7 +112,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 {
Expand All @@ -127,27 +132,27 @@ 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")
}
artifacts := arch.Artifacts
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")
}
Expand Down
2 changes: 1 addition & 1 deletion test/ignition.go → test-utils/ignition.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package e2e
package e2eutils

import (
"encoding/json"
Expand Down
2 changes: 1 addition & 1 deletion test/ignition_schema.go → test-utils/ignition_schema.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package e2e
package e2eutils

// Taken from https://github.com/coreos/ignition/blob/master/config/v3_2/types/schema.go

Expand Down
61 changes: 54 additions & 7 deletions test/pull.go → test-utils/pull.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package e2e
package e2eutils

import (
"compress/gzip"
"fmt"
"io"
"net/http"
Expand Down Expand Up @@ -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
Expand All @@ -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
}
Loading

0 comments on commit 25e8298

Please sign in to comment.