From b19a4f7fe904ea0e200f2eacbce789f59d19bd1f Mon Sep 17 00:00:00 2001 From: Luca Stocchi Date: Tue, 15 Oct 2024 11:17:46 +0200 Subject: [PATCH] test: add e2e test with fedora 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 --- test/assets/seed.img | Bin 0 -> 360448 bytes test/osprovider.go | 97 +++++++++++++++++++++++++++++++++++++++++++ test/vm_helpers.go | 7 +++- test/vm_test.go | 60 ++++++++++++++++++++++++++ 4 files changed, 163 insertions(+), 1 deletion(-) create mode 100644 test/assets/seed.img diff --git a/test/assets/seed.img b/test/assets/seed.img new file mode 100644 index 0000000000000000000000000000000000000000..6a978ed495cce20de766084884d522d63fc2ed97 GIT binary patch literal 360448 zcmeI)O>Y~=835pAkRk#Tq=%+(Q{*s60VhE#iL{+KX3+ypF0D;SF0o6w_DNtQ+7hBK zh-5y{^x#X1-uff*1A6YEKO_IbXf8RYyGu$zBLsDU!gZ5p0PW1q&g?t;5FoLOeHBFt z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAW-Xc8;xcy8ccRWnumMg zd&=&?-ef-co`XgAx#VA$ja7Btd%3PfQLX%o*4Co>@^JlU-wykE*?c{^vh2AMt(A?n zs8=f|J^x%ev78^2|Ea*>{UlF&c__BG-g&37xz)=zx4XU2OZstsKOe_~aC~(8=QVIL*SFowq{s)~)Ly-ngG`5ASr_{rJ4|W_WL7 z6t}ZH9d5q2k$1L|em@!BdD_>0t#R!}v(bFJ*=XFnasAz!YnM72-zm|h5w+-PuUfok zOJMn(W=r515cp>G`~M6q#S#b*AV7csfu|%;+`)zMVj9xjd1$^JnvGWDX6wfFP~NxH zuWvN2O&Yht`pIH`x|oOAbh@{3aP%;&e{^zquoqSj1=i0_c0UY@+4O9^dPs0IolmOP zQ@RFW2oNAZfB=F2MBs00wdxsr@ibi_K!5-N0t5&UAV7cs0RjYmumaVc|CRD}I)wlM z0t5&UAV7cs0RjXF5cr`A{N+-P-DmZm)?fJb+C`3CK6;_{_kToL_LJJ9(Y@c)j@qR~ z`=}UDmgl~%o*mS_er37;%6ZP-xcAH2%d3<9Yt@NgtNmg*dR0Zyc)zja_~KDH z_0boVLNVpL9OnU69M#!ZLC#}+9=*B@|LT{^`pe4e>cuLy^EkV-ii`IxpJWy10lxiE zuC7%<-dI*utYvUp0t5&U_%R5?+2dM${HNkqH5u&`>DSHncq>#IPIkUvT--*#(BuMN24?whhCAuJxcTBUX}VCR=MAKJZKNcNhcrm<8~g0 zPC6X7JLAwz@=h3S-|i>*R*@QBPR>VhC+Q`f_Bct0Ay2onPP`F99v4wB1{NWU)Af>K zPBJVOkqp||{ctz!Zx7;7ET>akM43unp71D5TpUw7nPCuS3=KHNMKU+-eyZh&J{}861oF1GN%dLin zKd%0v&=Xv~Ap{5zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF K5Fqe>BJfXvNU<{j literal 0 HcmV?d00001 diff --git a/test/osprovider.go b/test/osprovider.go index 4af16d27..240ee93e 100644 --- a/test/osprovider.go +++ b/test/osprovider.go @@ -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" @@ -42,6 +43,50 @@ 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() + + 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) @@ -64,6 +109,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 { @@ -143,6 +198,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 @@ -153,6 +220,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", @@ -174,3 +248,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, + }, + } +} diff --git a/test/vm_helpers.go b/test/vm_helpers.go index 5dc0bab8..4bce916b 100644 --- a/test/vm_helpers.go +++ b/test/vm_helpers.go @@ -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) } diff --git a/test/vm_test.go b/test/vm_test.go index 336ecea0..723c7f27 100644 --- a/test/vm_test.go +++ b/test/vm_test.go @@ -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) +}