Skip to content

Commit

Permalink
Merge pull request #207 from lstocchi/i123
Browse files Browse the repository at this point in the history
cloudinit: leverage virtio-blk to push cloud-init configuration
  • Loading branch information
openshift-merge-bot[bot] authored Oct 23, 2024
2 parents 1351d5a + b19a4f7 commit 4f9ec1f
Show file tree
Hide file tree
Showing 5 changed files with 182 additions and 2 deletions.
20 changes: 19 additions & 1 deletion doc/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,6 @@ Various devices can be added to the virtual machines. They are all paravirtualiz
The `--device virtio-blk` option adds a disk to the virtual machine. The disk is backed by an image file on the host machine. This file is a raw image file.
See also [vz/CreateDiskImage](https://pkg.go.dev/github.com/Code-Hex/vz/v3#CreateDiskImage).


#### Thin images

Apple Virtualization Framework only supports raw disk images and ISO images.
Expand All @@ -158,6 +157,20 @@ made to the copy-on-write image, and not to the backing file. Only the
modified data will use actual disk space.
A copy-on-write image can be created using `cp -c` or [clonefile(2)](http://www.manpagez.com/man/2/clonefile/).

#### Cloud-init

The `--device virtio-blk` option can also be used to supply an initial configuration to cloud-init through a disk image.

The ISO image file must be labelled cidata or CIDATA and it must contain the user-data and meta-data files.
It is also possible to add further configurations by using the network-config and vendor-data files.
See https://cloudinit.readthedocs.io/en/latest/reference/datasources/nocloud.html#runtime-configurations for more details.

To create the ISO image you can use the following command within a folder containing the user-data and meta-data files
```
mkisofs -output seed.img -volid cidata -rock {user-data,meta-data}
```

See https://cloudinit.readthedocs.io/en/latest/reference/datasources/nocloud.html#example-creating-a-disk for further details about how to create a disk image

#### Arguments
- `path`: the absolute path to the disk image file.
Expand All @@ -170,6 +183,11 @@ This adds a virtio-blk device to the VM which will be backed by the raw image at
--device virtio-blk,path=/Users/virtuser/vfkit.img
```

To also provide the cloud-init configuration you can add an additional virtio-blk device backed by an image containing the cloud-init configuration files
```
--device virtio-blk,path=/Users/virtuser/cloudinit.img
```


### NVM Express

Expand Down
Binary file added test/assets/seed.img
Binary file not shown.
97 changes: 97 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,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)
Expand All @@ -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 {
Expand Down Expand Up @@ -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

Expand All @@ -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",
Expand All @@ -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,
},
}
}
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 4f9ec1f

Please sign in to comment.