More specifically only those that are needed in /cmd/osbuild-image/tests. This patch can be merged with the previous one if we want to make sure every commit can be built, but I'm going to keep it like this for now so that we can easily see the changes.
271 lines
6.7 KiB
Go
271 lines
6.7 KiB
Go
// +build integration
|
|
|
|
package boot
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"log"
|
|
"os"
|
|
"os/exec"
|
|
"runtime"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/osbuild/osbuild-composer/cmd/osbuild-image-tests/constants"
|
|
"github.com/osbuild/osbuild-composer/internal/common"
|
|
"github.com/osbuild/osbuild-composer/internal/distro"
|
|
)
|
|
|
|
// 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,
|
|
constants.TestPaths.UserData,
|
|
constants.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
|
|
if common.CurrentArch() == "x86_64" {
|
|
hostDistroName, _, err := distro.GetHostDistroName()
|
|
if err != nil {
|
|
return fmt.Errorf("cannot determing the current distro: %v", err)
|
|
}
|
|
|
|
var qemuPath string
|
|
if strings.HasPrefix(hostDistroName, "rhel") {
|
|
qemuPath = "/usr/libexec/qemu-kvm"
|
|
} else {
|
|
qemuPath = "qemu-system-x86_64"
|
|
}
|
|
|
|
qemuCmd = ns.NamespacedCommand(
|
|
qemuPath,
|
|
"-cpu", "host",
|
|
"-smp", strconv.Itoa(runtime.NumCPU()),
|
|
"-m", "1024",
|
|
"-snapshot",
|
|
"-M", "accel=kvm",
|
|
"-cdrom", cloudInitFile.Name(),
|
|
"-net", "nic,model=rtl8139", "-net", "user,hostfwd=tcp::22-:22",
|
|
"-nographic",
|
|
image,
|
|
)
|
|
} else if common.CurrentArch() == "aarch64" {
|
|
// 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
|
|
qemuCmd = 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",
|
|
"-M", "accel=kvm",
|
|
"-snapshot",
|
|
"-cdrom", cloudInitFile.Name(),
|
|
"-net", "nic,model=rtl8139", "-net", "user,hostfwd=tcp::22-:22",
|
|
"-nographic",
|
|
image,
|
|
)
|
|
} 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 string, ns NetNS, f func() error) error {
|
|
cmd := exec.Command(
|
|
"systemd-nspawn",
|
|
"--boot", "--register=no",
|
|
"--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 string, ns NetNS, f func() error) error {
|
|
cmd := exec.Command(
|
|
"systemd-nspawn",
|
|
"--boot", "--register=no",
|
|
"--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)
|
|
})
|
|
}
|