Skip to content

Commit

Permalink
test: add e2e test with fedora
Browse files Browse the repository at this point in the history
it adds an e2e test which downloads fedora 40 and create a VM using it. Then mounts an ISO image containing the user-data and meta-data files needed by cloud-init to apply our configuration.
The meta-data file is empty but the user-data file contains info about a new user that has to be added to the system.
After the VM gets started it verifies that the user has been actually added by connecting using SSH.
At the end, the VM is stopped.
The test is not run on macOS12 as it does not support efi bootloader.

Signed-off-by: Luca Stocchi <[email protected]>
  • Loading branch information
Luca Stocchi authored and lstocchi committed Oct 23, 2024
1 parent 0ae085d commit 41e879f
Show file tree
Hide file tree
Showing 4 changed files with 172 additions and 1 deletion.
Binary file added test/assets/seed.img
Binary file not shown.
106 changes: 106 additions & 0 deletions test/osprovider.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"strings"

"github.com/crc-org/vfkit/pkg/config"
"github.com/xi2/xz"

"github.com/cavaliergopher/grab/v3"
"github.com/crc-org/crc/v2/pkg/extract"
Expand Down Expand Up @@ -42,6 +43,59 @@ func downloadPuipui(destDir string) ([]string, error) {
return extract.Uncompress(resp.Filename, destDir)
}

func downloadFedora(destDir string) (string, error) {
const fedoraVersion = "40"
arch := kernelArch()
release := "1.14"
buildString := fmt.Sprintf("%s-%s-%s", arch, fedoraVersion, release)

var fedoraURL = fmt.Sprintf("https://download.fedoraproject.org/pub/fedora/linux/releases/%s/Cloud/%s/images/Fedora-Cloud-Base-AmazonEC2.%s.raw.xz", fedoraVersion, arch, buildString)

// https://github.com/cavaliergopher/grab/issues/104
grab.DefaultClient.UserAgent = "vfkit"
resp, err := grab.Get(destDir, fedoraURL)
if err != nil {
return "", err
}
return uncompressFedora(resp.Filename, destDir)
}

func uncompressFedora(fileName string, targetDir string) (string, error) {
file, err := os.Open(filepath.Clean(fileName))
if err != nil {
return "", err
}
defer file.Close()

stat, err := file.Stat()
if err != nil {
return "", err
}
header := make([]byte, min(262, stat.Size()))
if _, err := io.ReadFull(file, header); err != nil {
return "", err
}

reader, err := xz.NewReader(file, 0)
if err != nil {
return "", err
}

xzCutName, _ := strings.CutSuffix(filepath.Base(file.Name()), ".xz")
outPath := filepath.Join(targetDir, xzCutName)
out, err := os.Create(outPath)
if err != nil {
return "", err
}

_, err = io.Copy(out, reader)
if err != nil {
return "", err
}

return outPath, nil
}

type OsProvider interface {
Fetch(destDir string) error
ToVirtualMachine() (*config.VirtualMachine, error)
Expand All @@ -64,6 +118,16 @@ func NewPuipuiProvider() *PuiPuiProvider {
return &PuiPuiProvider{}
}

type FedoraProvider struct {
diskImage string
efiVariableStorePath string
createVariableStore bool
}

func NewFedoraProvider() *FedoraProvider {
return &FedoraProvider{}
}

func findFile(files []string, filename string) (string, error) {
for _, f := range files {
if filepath.Base(f) == filename {
Expand Down Expand Up @@ -143,6 +207,18 @@ func (puipui *PuiPuiProvider) Fetch(destDir string) error {
return nil
}

func (fedora *FedoraProvider) Fetch(destDir string) error {
log.Infof("downloading fedora to %s", destDir)
file, err := downloadFedora(destDir)
if err != nil {
return err
}

fedora.diskImage = file

return nil
}

const puipuiMemoryMiB = 1 * 1024
const puipuiCPUs = 2

Expand All @@ -153,6 +229,13 @@ func (puipui *PuiPuiProvider) ToVirtualMachine() (*config.VirtualMachine, error)
return vm, nil
}

func (fedora *FedoraProvider) ToVirtualMachine() (*config.VirtualMachine, error) {
bootloader := config.NewEFIBootloader(fedora.efiVariableStorePath, fedora.createVariableStore)
vm := config.NewVirtualMachine(puipuiCPUs, puipuiMemoryMiB, bootloader)

return vm, nil
}

func (puipui *PuiPuiProvider) SSHConfig() *ssh.ClientConfig {
return &ssh.ClientConfig{
User: "root",
Expand All @@ -174,3 +257,26 @@ func (puipui *PuiPuiProvider) SSHAccessMethods() []SSHAccessMethod {
},
}
}

func (fedora *FedoraProvider) SSHConfig() *ssh.ClientConfig {
return &ssh.ClientConfig{
User: "vfkituser",
Auth: []ssh.AuthMethod{ssh.Password("vfkittest")},
// #nosec 106 -- the host SSH key of the VM will change each time it boots
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}

}

func (fedora *FedoraProvider) SSHAccessMethods() []SSHAccessMethod {
return []SSHAccessMethod{
{
network: "tcp",
port: 22,
},
{
network: "vsock",
port: 2222,
},
}
}
7 changes: 6 additions & 1 deletion test/vm_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,12 @@ func (vm *testVM) Start(t *testing.T) {
}

func (vm *testVM) Stop(t *testing.T) {
vm.SSHRun(t, "poweroff")
switch vm.provider.(type) {
case *FedoraProvider:
vm.SSHRun(t, "sudo shutdown now")
default:
vm.SSHRun(t, "poweroff")
}
vm.vfkitCmd.Wait(t)
}

Expand Down
60 changes: 60 additions & 0 deletions test/vm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -341,3 +341,63 @@ func checkPCIDevice(t *testing.T, vm *testVM, vendorID, deviceID int) {
require.NoError(t, err)
require.Regexp(t, re, string(lspci))
}

func TestCloudInit(t *testing.T) {
if err := macOSAvailable(13); err != nil {
t.Log("Skipping TestCloudInit test")
return
}
fedoraProvider := NewFedoraProvider()
log.Info("fetching os image")
tempDir := t.TempDir()
err := fedoraProvider.Fetch(tempDir)
require.NoError(t, err)

// set efi bootloader
fedoraProvider.efiVariableStorePath = "efi-variable-store"
fedoraProvider.createVariableStore = true

vm := NewTestVM(t, fedoraProvider)
defer vm.Close(t)
require.NotNil(t, vm)

vm.AddSSH(t, "tcp")

// add vm image
dev1, err := config.VirtioBlkNew(fedoraProvider.diskImage)
require.NoError(t, err)
vm.AddDevice(t, dev1)
log.Infof("shared disk: %s - fedora", dev1.DevName)

/* add cloud init config by using a premade ISO image
seed.img is an ISO image containing the user-data and meta-data file needed to configure the VM by cloud-init.
meta-data is an empty file
user-data has info about a new user that will be used to verify if the configuration has been applied. Its content is
----
#cloud-config
users:
- name: vfkituser
sudo: ALL=(ALL) NOPASSWD:ALL
shell: /bin/bash
groups: users
plain_text_passwd: vfkittest
lock_passwd: false
ssh_pwauth: true
chpasswd: { expire: false }
*/
dev, err := config.VirtioBlkNew("assets/seed.img")
require.NoError(t, err)
vm.AddDevice(t, dev)
log.Infof("shared disk: %s - cloud-init", dev.DevName)

vm.Start(t)
vm.WaitForSSH(t)

data, err := vm.SSHCombinedOutput(t, "whoami")
require.NoError(t, err)
log.Infof("executed whoami - output: %s", string(data))
require.Equal(t, "vfkituser\n", string(data))

log.Info("stopping vm")
vm.Stop(t)
}

0 comments on commit 41e879f

Please sign in to comment.