Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions test/e2e/boot_failure_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,9 @@ func TestE2ECloudHypervisorKernelWithoutPVH(t *testing.T) {

log.Printf("TEST INFO: no VMM is running for MicroVM %s/%s and its guest has not booted", mvmNS, mvmID)

// The VMM has exited, so waitForBoot does not wait for the marker.
expectWaitForBootToFail(provider, microVMPath, mvmID, mvmNS, vmmPid, noBootDuration, vmmNotRunningReason)

// flintlockd removes the state directory when the microvm is deleted.
saveArtefacts(t, microVMPath)

Expand Down Expand Up @@ -283,6 +286,15 @@ func runGuestWithoutInit(t *testing.T, flintlockClient v1alpha1.MicroVMClient, p

log.Printf("TEST INFO: the guest of MicroVM %s/%s has not booted", mvmNS, mvmID)

// A VMM which keeps running makes waitForBoot wait for the marker until
// its timeout. This is the only guest for which it gets that far.
reason := vmmNotRunningReason
if provider.ResetsGuest {
reason = noMarkerReason
}

expectWaitForBootToFail(provider, microVMPath, mvmID, mvmNS, vmmPid, noBootDuration, reason)

// flintlockd removes the state directory when the microvm is deleted.
saveArtefacts(t, microVMPath)

Expand Down Expand Up @@ -312,6 +324,33 @@ func runGuestWithoutInit(t *testing.T, flintlockClient v1alpha1.MicroVMClient, p
mvmNS, mvmID, time.Since(deleteStart).Round(time.Millisecond))
}

const (
// vmmNotRunningReason is in the failure of waitForBoot for a VMM which has
// exited. It is the message of the StopTrying in waitForBoot.
vmmNotRunningReason = "did not keep running"
// noMarkerReason is in the failure of waitForBoot for a VMM which runs
// without a guest that has booted. It is from the error of ConsoleMarker.
noMarkerReason = "boot marker"
)

// expectWaitForBootToFail checks that waitForBoot, which the tests of a guest
// that boots rely on, fails for this guest, and fails for the reason given.
// Without this, a waitForBoot which passes for every guest would fail no test.
func expectWaitForBootToFail(provider u.Provider, stateDir, name, namespace string, vmmPid int, timeout, reason string) {
log.Printf("TEST STEP: verifying that waitForBoot does not pass within %s", timeout)

// InterceptGomegaFailure stops waitForBoot at its first failed assertion
// and returns the failure, so the log lines of a guest which has booted
// are not printed.
err := InterceptGomegaFailure(func() {
waitForBoot(provider, stateDir, name, namespace, vmmPid, timeout)
})
Expect(err).To(HaveOccurred(), "waitForBoot passed for a guest which has not booted")
Expect(err).To(MatchError(ContainSubstring(reason)), "waitForBoot failed for another reason")

log.Printf("TEST INFO: waitForBoot did not pass: %s", err)
}

// selected returns true if the provider is one of the providers which the
// tests run with.
func selected(provider u.Provider) bool {
Expand Down
10 changes: 7 additions & 3 deletions test/e2e/checks_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,12 @@ const (
// show that the guest has booted: a VMM keeps running when its guest has no
// kernel that it can boot, or when the kernel cannot find its root volume.
//
// The vmmPid is the pid of the VMM from when the microvm was CREATED.
func waitForBoot(provider u.Provider, stateDir, name, namespace string, vmmPid int) {
// The vmmPid is the pid of the VMM from when the microvm was CREATED. The
// timeout is how long the guest gets, bootTimeout for a guest which boots.
//
// It fails the test when the guest has not booted, which the tests of a
// guest that cannot boot rely on.
func waitForBoot(provider u.Provider, stateDir, name, namespace string, vmmPid int, timeout string) {
log.Printf("TEST STEP: waiting for the guest of MicroVM %s/%s to boot", namespace, name)

start := time.Now()
Expand All @@ -53,7 +57,7 @@ func waitForBoot(provider u.Provider, stateDir, name, namespace string, vmmPid i
marker = line

return nil
}, bootTimeout, bootPolling).Should(Succeed())
}, timeout, bootPolling).Should(Succeed())

// The console is appended to when flintlockd starts the VMM again, so the
// marker could be from a VMM which has stopped since.
Expand Down
6 changes: 3 additions & 3 deletions test/e2e/e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ func runLifecycle(t *testing.T, flintlockClient v1alpha1.MicroVMClient, provider
return nil
}, "120s").Should(Succeed())

waitForBoot(provider, firstMicroVMPath, mvmID, mvmNS, mvmPid1)
waitForBoot(provider, firstMicroVMPath, mvmID, mvmNS, mvmPid1, bootTimeout)

log.Println("TEST STEP: creating a second MicroVM")
createdSecond := u.CreateMVM(flintlockClient, provider, secondMvmID, mvmNS)
Expand Down Expand Up @@ -160,7 +160,7 @@ func runLifecycle(t *testing.T, flintlockClient v1alpha1.MicroVMClient, provider
return nil
}, "120s").Should(Succeed())

waitForBoot(provider, secondMicroVMPath, secondMvmID, mvmNS, mvmPid2)
waitForBoot(provider, secondMicroVMPath, secondMvmID, mvmNS, mvmPid2, bootTimeout)

log.Println("TEST STEP: creating a MicroVM with a long namespace and name and the guest agent enabled")
createdLong := u.CreateGuestAgentMVM(flintlockClient, provider, longMvmID, longMvmNS)
Expand Down Expand Up @@ -189,7 +189,7 @@ func runLifecycle(t *testing.T, flintlockClient v1alpha1.MicroVMClient, provider
return nil
}, "120s").Should(Succeed())

waitForBoot(provider, longMicroVMPath, longMvmID, longMvmNS, mvmPid3)
waitForBoot(provider, longMicroVMPath, longMvmID, longMvmNS, mvmPid3, bootTimeout)

// flintlockd removes the state directory when the microvm is deleted.
saveArtefacts(t, firstMicroVMPath)
Expand Down
36 changes: 21 additions & 15 deletions test/e2e/harness_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,24 +111,24 @@ func TestParseProviders(t *testing.T) {
}
}

// The spec names the provider and boots the guest with the kernel of the
// provider. Which kernel that is, is a fact of the provider and not of this
// test.
func TestNewMicroVMSpecUsesTheProvider(t *testing.T) {
g := NewWithT(t)

spec, err := u.NewMicroVMSpec(u.CloudHypervisor(), "mvm0", "ns0")
g.Expect(err).NotTo(HaveOccurred())

g.Expect(spec.Id).To(Equal("mvm0"))
g.Expect(spec.Namespace).To(Equal("ns0"))
g.Expect(spec.Provider).To(HaveValue(Equal("cloudhypervisor")))
g.Expect(spec.Kernel.Image).To(Equal("ghcr.io/liquidmetal-dev/cloudhypervisor-kernel-bin:5.12"))
g.Expect(spec.Kernel.Filename).To(HaveValue(Equal("boot/vmlinux.bin")))
for _, provider := range u.KnownProviders() {
t.Run(provider.Name, func(t *testing.T) {
g := NewWithT(t)

spec, err = u.NewMicroVMSpec(u.Firecracker(), "mvm0", "ns0")
g.Expect(err).NotTo(HaveOccurred())
spec, err := u.NewMicroVMSpec(provider, "mvm0", "ns0")
g.Expect(err).NotTo(HaveOccurred())

g.Expect(spec.Provider).To(HaveValue(Equal("firecracker")))
g.Expect(spec.Kernel.Image).To(Equal("ghcr.io/liquidmetal-dev/firecracker-kernel:6.1"))
g.Expect(spec.Kernel.Filename).To(HaveValue(Equal("boot/vmlinux")))
g.Expect(spec.Id).To(Equal("mvm0"))
g.Expect(spec.Namespace).To(Equal("ns0"))
g.Expect(spec.Provider).To(HaveValue(Equal(provider.Name)))
g.Expect(spec.Kernel.Image).To(Equal(provider.KernelImage))
g.Expect(spec.Kernel.Filename).To(HaveValue(Equal(provider.KernelFilename)))
})
}
}

// The test of a guest which cannot boot needs a kernel which Cloud Hypervisor
Expand Down Expand Up @@ -473,6 +473,12 @@ func TestConsoleMarker(t *testing.T) {
console: ptr.String("flintlock-e2e boot ok ns0/mvm0 uptime=14\n"),
expected: "flintlock-e2e boot ok ns0/mvm0 uptime=14",
},
{
// A colour code can come after the uptime and before the marker.
name: "escape sequences next to the marker",
console: ptr.String("\x1b[32m" + booted + "\x1b[0m\n"),
expected: "\x1b[32m" + booted + "\x1b[0m",
},
{
name: "no console file",
expectErr: true,
Expand Down
2 changes: 1 addition & 1 deletion test/e2e/private_registry_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ func TestE2EPrivateRegistry(t *testing.T) {
log.Printf("TEST INFO: MicroVM %s/%s is running with %s pid %d, it took %s",
mvmNS, mvmID, provider.Name, mvmPid, time.Since(createStart).Round(time.Millisecond))

waitForBoot(provider, microVMPath, mvmID, mvmNS, mvmPid)
waitForBoot(provider, microVMPath, mvmID, mvmNS, mvmPid, bootTimeout)

log.Println("TEST STEP: verifying that the images were pulled from the private registry")
for _, image := range []string{kernelImage, rootImage} {
Expand Down
7 changes: 6 additions & 1 deletion test/e2e/utils/checks.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,13 @@ func ConsoleMarker(stateDir string, provider Provider, name, namespace string) (

// The uptime has to be a number. If it is not, the line is the user-data
// which something has printed, and not the message of cloud-init.
//
// There is nothing before or after the marker in the pattern: an escape
// sequence can be next to it. A longer namespace or name cannot match, as
// the marker starts with a fixed text and has the namespace and the name
// between fixed texts.
marker := regexp.MustCompile(
`(^|\s)` + regexp.QuoteMeta(fmt.Sprintf(bootMarkerFormat, namespace, name)) + `[0-9]+(\.[0-9]+)?(\s|$)`)
regexp.QuoteMeta(fmt.Sprintf(bootMarkerFormat, namespace, name)) + `[0-9]+(\.[0-9]+)?`)

for _, line := range bytes.Split(console, []byte("\n")) {
if marker.Match(line) {
Expand Down
Loading