debian-forge-composer/cmd/osbuild-image-tests/context-managers.go
Major Hayden 3740e9bc6b 🐎 Allow qemu to use all available CPUs
Speed up the boot tests by allowing qemu to use all of the available
CPUs on the system. Our CI VMs have at least 2 CPUs and this shortens
the time required for a boot test.

Signed-off-by: Major Hayden <major@redhat.com>
2020-04-17 17:10:56 +02:00

261 lines
6.3 KiB
Go

// +build integration
package main
import (
"fmt"
"github.com/osbuild/osbuild-composer/internal/common"
"io"
"io/ioutil"
"log"
"os"
"os/exec"
"runtime"
"strconv"
"time"
)
// withNetworkNamespace provides the function f with a new network namespace
// which is deleted immediately after f returns
func withNetworkNamespace(f func(ns netNS) error) error {
ns, err := newNetworkNamespace()
if err != nil {
return err
}
defer func() {
err := ns.Delete()
if err != nil {
log.Printf("cannot delete network namespace: %#v", err)
}
}()
return f(ns)
}
// withTempFile provides the function f with a new temporary file
// dir and pattern parameters have the same semantics as in ioutil.TempFile
func withTempFile(dir, pattern string, f func(file *os.File) error) error {
tempFile, err := ioutil.TempFile(dir, pattern)
if err != nil {
return fmt.Errorf("cannot create the temporary file: %#v", err)
}
defer func() {
err := os.Remove(tempFile.Name())
if err != nil {
log.Printf("cannot remove the temporary file: %#v", err)
}
}()
return f(tempFile)
}
func withTempDir(dir, pattern string, f func(dir string) error) error {
tempDir, err := ioutil.TempDir(dir, pattern)
if err != nil {
return fmt.Errorf("cannot create the temporary directory %#v", err)
}
defer func() {
err := os.RemoveAll(tempDir)
if err != nil {
log.Printf("cannot remove the temporary directory: %#v", err)
}
}()
return f(tempDir)
}
// writeCloudInitSO creates cloud-init iso from specified userData and
// metaData and writes it to the writer
func writeCloudInitISO(writer io.Writer, userData, metaData string) error {
isoCmd := exec.Command(
"genisoimage",
"-quiet",
"-input-charset", "utf-8",
"-volid", "cidata",
"-joliet",
"-rock",
userData,
metaData,
)
isoCmd.Stdout = writer
isoCmd.Stderr = os.Stderr
err := isoCmd.Run()
if err != nil {
return fmt.Errorf("cannot create cloud-init iso: %#v", err)
}
return nil
}
// withBootedQemuImage boots the specified image in the specified namespace
// using qemu. The VM is killed immediately after function returns.
func withBootedQemuImage(image string, ns netNS, f func() error) error {
return withTempFile("", "osbuild-image-tests-cloudinit", func(cloudInitFile *os.File) error {
err := writeCloudInitISO(
cloudInitFile,
testPaths.userData,
testPaths.metaData,
)
if err != nil {
return err
}
err = cloudInitFile.Close()
if err != nil {
return fmt.Errorf("cannot close temporary cloudinit file: %#v", err)
}
var qemuCmd *exec.Cmd
// This command does not use KVM as I was unable to make it work in Beaker,
// once we have machines that can use KVM, enable it to make it faster
qemuAarch64Cmd := ns.NamespacedCommand(
"qemu-system-aarch64",
"-cpu", "host",
"-M", "virt",
"-m", "2048",
// As opposed to x86_64, aarch64 uses UEFI, this one comes from edk2-aarch64 package on Fedora
"-bios", "/usr/share/edk2/aarch64/QEMU_EFI.fd",
"-boot", "efi",
"-accel", "accel=kvm",
"-snapshot",
"-cdrom", cloudInitFile.Name(),
"-net", "nic,model=rtl8139", "-net", "user,hostfwd=tcp::22-:22",
"-nographic",
image,
)
qemuX8664Cmd := ns.NamespacedCommand(
"qemu-system-x86_64",
"-cpu", "host",
"-smp", strconv.Itoa(runtime.NumCPU()),
"-m", "1024",
"-snapshot",
"-accel", "accel=kvm:hvf:tcg",
"-cdrom", cloudInitFile.Name(),
"-net", "nic,model=rtl8139", "-net", "user,hostfwd=tcp::22-:22",
"-nographic",
image,
)
if common.CurrentArch() == "x86_64" {
qemuCmd = qemuX8664Cmd
} else if common.CurrentArch() == "aarch64" {
qemuCmd = qemuAarch64Cmd
} else {
panic("Running on unknown architecture.")
}
err = qemuCmd.Start()
if err != nil {
return fmt.Errorf("cannot start the qemu process: %#v", err)
}
defer func() {
err := killProcessCleanly(qemuCmd.Process, time.Second)
if err != nil {
log.Printf("cannot kill the qemu process: %#v", err)
}
}()
return f()
})
}
// withBootedNspawnImage boots the specified image in the specified namespace
// using nspawn. The VM is killed immediately after function returns.
func withBootedNspawnImage(image, name string, ns netNS, f func() error) error {
cmd := exec.Command(
"systemd-nspawn",
"--boot", "--register=no",
"-M", name,
"--image", image,
"--network-namespace-path", ns.Path(),
)
err := cmd.Start()
if err != nil {
return fmt.Errorf("cannot start the systemd-nspawn process: %#v", err)
}
defer func() {
err := killProcessCleanly(cmd.Process, time.Second)
if err != nil {
log.Printf("cannot kill the systemd-nspawn process: %#v", err)
}
}()
return f()
}
// withBootedNspawnImage boots the specified directory in the specified namespace
// using nspawn. The VM is killed immediately after function returns.
func withBootedNspawnDirectory(dir, name string, ns netNS, f func() error) error {
cmd := exec.Command(
"systemd-nspawn",
"--boot", "--register=no",
"-M", name,
"--directory", dir,
"--network-namespace-path", ns.Path(),
)
err := cmd.Start()
if err != nil {
return fmt.Errorf("cannot start the systemd-nspawn process: %#v", err)
}
defer func() {
err := killProcessCleanly(cmd.Process, time.Second)
if err != nil {
log.Printf("cannot kill the systemd-nspawn process: %#v", err)
}
}()
return f()
}
// withExtractedTarArchive extracts the provided archive and passes
// a path to the result to the function f. The result is deleted
// immediately after the function returns.
func withExtractedTarArchive(archive string, f func(dir string) error) error {
return withTempDir("", "tar-archive", func(dir string) error {
cmd := exec.Command(
"tar",
"xf", archive,
"-C", dir,
)
cmd.Stderr = os.Stderr
cmd.Stdout = os.Stdout
err := cmd.Run()
if err != nil {
return fmt.Errorf("cannot untar the archive: %#v", err)
}
return f(dir)
})
}
// withSSHKeyPair runs the function f with a newly generated
// ssh key-pair, they key-pair is deleted immediately after
// the function f returns
func withSSHKeyPair(f func(privateKey, publicKey string) error) error {
return withTempDir("", "keys", func(dir string) error {
privateKey := dir + "/id_rsa"
publicKey := dir + "/id_rsa.pub"
cmd := exec.Command("ssh-keygen",
"-N", "",
"-f", privateKey,
)
err := cmd.Run()
if err != nil {
return fmt.Errorf("ssh-keygen failed: %#v", err)
}
return f(privateKey, publicKey)
})
}