This repository has been archived by the owner on Dec 16, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed-off-by: Itxaka <itxaka@kairos.io>
- Loading branch information
Showing
5 changed files
with
373 additions
and
25 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,263 @@ | ||
package e2e_test | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"github.com/google/uuid" | ||
process "github.com/mudler/go-processmanager" | ||
. "github.com/onsi/ginkgo/v2" | ||
. "github.com/onsi/gomega" | ||
"github.com/sanity-io/litter" | ||
. "github.com/spectrocloud/peg/matcher" | ||
"github.com/spectrocloud/peg/pkg/machine" | ||
"github.com/spectrocloud/peg/pkg/machine/types" | ||
"net" | ||
"os" | ||
"os/exec" | ||
"path" | ||
"path/filepath" | ||
"strconv" | ||
) | ||
|
||
var _ = Describe("bootable artifacts", Label("bootable"), func() { | ||
When("when running a built iso", func() { | ||
var vm VM | ||
|
||
BeforeEach(func() { | ||
_, ok := os.Stat(os.Getenv("ISO")) | ||
Expect(ok).To(BeNil(), "ISO should exist") | ||
vm, err := startVM() | ||
Expect(err).ToNot(HaveOccurred()) | ||
By("Before connects") | ||
vm.EventuallyConnects(1200) | ||
By("After connects") | ||
}) | ||
AfterEach(func() { | ||
serial, _ := os.ReadFile(filepath.Join(vm.StateDir, "serial.log")) | ||
//_ = os.MkdirAll("logs", os.ModePerm|os.ModeDir) | ||
//_ = os.WriteFile(filepath.Join("logs", "serial.log"), serial, os.ModePerm) | ||
GinkgoWriter.Print(serial) | ||
|
||
err := vm.Destroy(nil) | ||
Expect(err).ToNot(HaveOccurred()) | ||
}) | ||
It("Should boot as expected", func() { | ||
By("Have secureboot enabled", func() { | ||
//sudo, err := Sudo("dmesg | grep -i secure") | ||
//fmt.Fprintf(GinkgoWriter, sudo) | ||
//Expect(err).ToNot(HaveOccurred()) | ||
//output, err := vm.Sudo("dmesg | grep -i secure") | ||
//Expect(err).ToNot(HaveOccurred(), output) | ||
//Expect(output).To(ContainSubstring("Secure boot enabled")) | ||
}) | ||
|
||
By("Have our custom keys only", func() { | ||
//output, err := vm.Sudo("kairos-agent state get \"kairos.eficerts|tojson\"") | ||
//Expect(err).ToNot(HaveOccurred(), output) | ||
//fmt.Println(output) | ||
//Expect(output).To(ContainSubstring("CIKEYS")) | ||
}) | ||
By("Before second connect") | ||
vm.EventuallyConnects(1200) | ||
By("After second connect") | ||
|
||
Expect(nil).To(BeNil()) | ||
}) | ||
}) | ||
}) | ||
|
||
func emulateTPM(stateDir string) { | ||
t := path.Join(stateDir, "tpm") | ||
err := os.MkdirAll(t, os.ModePerm) | ||
Expect(err).ToNot(HaveOccurred()) | ||
|
||
cmd := exec.Command("swtpm", | ||
"socket", | ||
"--tpmstate", fmt.Sprintf("dir=%s", t), | ||
"--ctrl", fmt.Sprintf("type=unixio,path=%s/swtpm-sock", t), | ||
"--tpm2", "--log", "level=20") | ||
err = cmd.Start() | ||
Expect(err).ToNot(HaveOccurred()) | ||
|
||
err = os.WriteFile(path.Join(t, "pid"), []byte(strconv.Itoa(cmd.Process.Pid)), 0744) | ||
Expect(err).ToNot(HaveOccurred()) | ||
} | ||
|
||
func startVM() (VM, error) { | ||
stateDir, err := os.MkdirTemp("", "") | ||
Expect(err).ToNot(HaveOccurred()) | ||
fmt.Printf("State dir: %s\n", stateDir) | ||
|
||
opts := defaultVMOpts(stateDir) | ||
|
||
m, err := machine.New(opts...) | ||
Expect(err).ToNot(HaveOccurred()) | ||
fmt.Println(litter.Sdump(m.Config())) | ||
|
||
vm := NewVM(m, stateDir) | ||
_, err = vm.Start(context.Background()) | ||
return vm, err | ||
} | ||
|
||
func defaultVMOpts(stateDir string) []types.MachineOption { | ||
opts := defaultVMOptsNoDrives(stateDir) | ||
|
||
driveSize := os.Getenv("DRIVE_SIZE") | ||
if driveSize == "" { | ||
driveSize = "25000" | ||
} | ||
|
||
opts = append(opts, types.WithDriveSize(driveSize)) | ||
|
||
return opts | ||
} | ||
|
||
func defaultVMOptsNoDrives(stateDir string) []types.MachineOption { | ||
var err error | ||
|
||
if os.Getenv("ISO") == "" && os.Getenv("CREATE_VM") == "true" { | ||
fmt.Println("ISO missing") | ||
os.Exit(1) | ||
} | ||
|
||
var sshPort, spicePort int | ||
|
||
vmName := uuid.New().String() | ||
|
||
emulateTPM(stateDir) | ||
|
||
sshPort, err = getFreePort() | ||
Expect(err).ToNot(HaveOccurred()) | ||
fmt.Printf("Using ssh port: %d\n", sshPort) | ||
|
||
memory := os.Getenv("MEMORY") | ||
if memory == "" { | ||
memory = "2096" | ||
} | ||
cpus := os.Getenv("CPUS") | ||
if cpus == "" { | ||
cpus = "2" | ||
} | ||
|
||
opts := []types.MachineOption{ | ||
types.QEMUEngine, | ||
types.WithISO(os.Getenv("ISO")), | ||
types.WithMemory(memory), | ||
types.WithCPU(cpus), | ||
types.WithSSHPort(strconv.Itoa(sshPort)), | ||
types.WithID(vmName), | ||
types.WithSSHUser("kairos"), | ||
types.WithSSHPass("kairos"), | ||
types.OnFailure(func(p *process.Process) { | ||
var serial string | ||
|
||
out, _ := os.ReadFile(p.StdoutPath()) | ||
err, _ := os.ReadFile(p.StderrPath()) | ||
status, _ := p.ExitCode() | ||
|
||
if serialBytes, err := os.ReadFile(path.Join(p.StateDir(), "serial.log")); err != nil { | ||
serial = fmt.Sprintf("Error reading serial log file: %s\n", err) | ||
} else { | ||
serial = string(serialBytes) | ||
} | ||
|
||
// We are explicitly killing the qemu process. We don't treat that as an error, | ||
// but we just print the output just in case. | ||
fmt.Printf("\nVM Aborted.\nstdout: %s\nstderr: %s\nserial: %s\nExit status: %s\n", out, err, serial, status) | ||
Fail(fmt.Sprintf("\nVM Aborted.\nstdout: %s\nstderr: %s\nserial: %s\nExit status: %s\n", | ||
out, err, serial, status)) | ||
}), | ||
types.WithStateDir(stateDir), | ||
// Serial output to file: https://superuser.com/a/1412150 | ||
func(m *types.MachineConfig) error { | ||
m.Args = append(m.Args, | ||
"-chardev", fmt.Sprintf("stdio,mux=on,id=char0,logfile=%s,signal=off", path.Join(stateDir, "serial.log")), | ||
"-serial", "chardev:char0", | ||
"-mon", "chardev=char0", | ||
) | ||
m.Args = append(m.Args, | ||
"-chardev", fmt.Sprintf("socket,id=chrtpm,path=%s/swtpm-sock", path.Join(stateDir, "tpm")), | ||
"-tpmdev", "emulator,id=tpm0,chardev=chrtpm", "-device", "tpm-tis,tpmdev=tpm0", | ||
) | ||
return nil | ||
}, | ||
// Firmware | ||
func(m *types.MachineConfig) error { | ||
FW := os.Getenv("FIRMWARE") | ||
if FW != "" { | ||
getwd, err := os.Getwd() | ||
if err != nil { | ||
return err | ||
} | ||
m.Args = append(m.Args, "-drive", | ||
fmt.Sprintf("file=%s,if=pflash,format=raw,readonly=on", FW), | ||
) | ||
|
||
// Copy the empty efivars to not modify it | ||
f, err := os.ReadFile(filepath.Join(getwd, "assets/efivars.empty.fd")) | ||
if err != nil { | ||
return err | ||
} | ||
err = os.WriteFile(filepath.Join(stateDir, "efivars.empty.fd"), f, os.ModePerm) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
m.Args = append(m.Args, "-drive", | ||
fmt.Sprintf("file=%s,if=pflash,format=raw", filepath.Join(stateDir, "efivars.empty.fd")), | ||
) | ||
|
||
// Needed to be set for secureboot! | ||
m.Args = append(m.Args, "-machine", "q35,smm=on") | ||
} | ||
|
||
return nil | ||
}, | ||
types.WithDataSource(os.Getenv("DATASOURCE")), | ||
} | ||
if os.Getenv("KVM") != "" { | ||
opts = append(opts, func(m *types.MachineConfig) error { | ||
m.Args = append(m.Args, | ||
"-enable-kvm", | ||
) | ||
return nil | ||
}) | ||
} | ||
|
||
if os.Getenv("USE_QEMU") == "true" { | ||
opts = append(opts, types.QEMUEngine) | ||
|
||
// You can connect to it with "spicy" or other tool. | ||
// DISPLAY is already taken on Linux X sessions | ||
if os.Getenv("MACHINE_SPICY") != "" { | ||
spicePort, _ = getFreePort() | ||
for spicePort == sshPort { // avoid collision | ||
spicePort, _ = getFreePort() | ||
} | ||
display := fmt.Sprintf("-spice port=%d,addr=127.0.0.1,disable-ticketing=yes", spicePort) | ||
opts = append(opts, types.WithDisplay(display)) | ||
|
||
cmd := exec.Command("spicy", | ||
"-h", "127.0.0.1", | ||
"-p", strconv.Itoa(spicePort)) | ||
err = cmd.Start() | ||
Expect(err).ToNot(HaveOccurred()) | ||
} | ||
} else { | ||
opts = append(opts, types.VBoxEngine) | ||
} | ||
|
||
return opts | ||
} | ||
|
||
func getFreePort() (port int, err error) { | ||
var a *net.TCPAddr | ||
if a, err = net.ResolveTCPAddr("tcp", "localhost:0"); err == nil { | ||
var l *net.TCPListener | ||
if l, err = net.ListenTCP("tcp", a); err == nil { | ||
defer l.Close() | ||
return l.Addr().(*net.TCPAddr).Port, nil | ||
} | ||
} | ||
return | ||
} |
Oops, something went wrong.