From 0fc0ba93eb4f5be55ef3bc3320322637812e1e7f Mon Sep 17 00:00:00 2001 From: Giovanni Ferri Date: Wed, 8 Jul 2026 23:13:58 +0100 Subject: [PATCH] test(e2e): cover ScaleToZero API + pending datapath spec MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add smoke e2e for the Phase 3 ScaleToZero API surface (installed via the chart CRDs, enforced by the validating webhook), which had no e2e coverage: reject an idleTimeout below the 10s floor, and admit a ScaleToZero ImpVM with the experimental admission warning. Control-plane only, so they run in the agent-off Kind smoke harness. Add a deliberately Pending (PIt) datapath spec documenting the wake-on-traffic flow (suspend -> frame -> resume). It cannot run until a KVM node + real Firecracker agent exist and BeforeSuite conditionally enables the agent; the traffic-injection source is an open question for first run. The runbook carries that harness work — this spec is the placeholder it will fill in. --- test/e2e/scaletozero_datapath_test.go | 59 +++++++++++++++++++ test/e2e/scaletozero_test.go | 81 +++++++++++++++++++++++++++ 2 files changed, 140 insertions(+) create mode 100644 test/e2e/scaletozero_datapath_test.go create mode 100644 test/e2e/scaletozero_test.go diff --git a/test/e2e/scaletozero_datapath_test.go b/test/e2e/scaletozero_datapath_test.go new file mode 100644 index 0000000..43bcb71 --- /dev/null +++ b/test/e2e/scaletozero_datapath_test.go @@ -0,0 +1,59 @@ +//go:build e2e +// +build e2e + +/* +Copyright 2026. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package e2e + +import ( + . "github.com/onsi/ginkgo/v2" +) + +// Wake-on-traffic datapath e2e — DELIBERATELY Pending (PIt). +// +// This validates the one thing the host-only unit + envtest coverage cannot: that +// the agent's AF_PACKET hook actually observes the first frame destined to a +// TAP-less (suspended) ScaleToZero VM on imp's VXLAN overlay, and that a real +// Firecracker suspend/resume round-trips. It is UNVALIDATED and cannot run yet — +// see docs (runner runbook) for why the harness must change before it can. +// +// Prerequisites that do NOT exist in the current e2e harness (all are runbook +// decisions, which is why this spec stays Pending until the runbook lands them): +// +// 1. A KVM-capable node. BeforeSuite currently pins the agent to +// nodeSelector imp.dev/no-agent=true (agent off) and the Kind smoke runner has +// no /dev/kvm. A real Firecracker agent needs nested virt + the Firecracker +// Talos system extension + a guest kernel at agent.env.kernelPath. +// 2. The agent enabled with IMP_SCALE_TO_ZERO=true. BeforeSuite must conditionally +// drop the no-agent selector and set the env when targeting the KVM runner +// (e.g. gate on an IMP_E2E_REAL_AGENT env var). +// 3. A traffic source on the same ImpNetwork. OPEN QUESTION for first run: how does +// a frame reach a suspended VM's overlay IP? The realistic source is a second +// always-on VM on the same ImpNetwork pinging the suspended VM — but that pulls +// in guest exec / vsock. This is the crux to resolve when the runner is live. +// +// Intended flow once the harness supports it: +// - Create an ImpNetwork + a ScaleToZero ImpVM with a short idleTimeout (e.g. 15s). +// - Wait for Running, then (no traffic) wait for the agent's idle detector to +// auto-suspend it → status.phase == Suspended, VTEP retained. +// - Send a frame to the VM's overlay IP from the traffic source. +// - Assert the VM returns to Running (Suspended → Resuming → Running). +var _ = Describe("Imp ScaleToZero datapath", Label("datapath"), func() { + PIt("wakes a suspended ScaleToZero VM when a frame arrives for its overlay IP", func() { + Skip("requires a KVM node + real Firecracker agent; see the e2e runner runbook") + }) +}) diff --git a/test/e2e/scaletozero_test.go b/test/e2e/scaletozero_test.go new file mode 100644 index 0000000..61d6df8 --- /dev/null +++ b/test/e2e/scaletozero_test.go @@ -0,0 +1,81 @@ +//go:build e2e +// +build e2e + +/* +Copyright 2026. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package e2e + +import ( + "fmt" + "os/exec" + "strings" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "github.com/syscode-labs/imp/test/utils" +) + +// These cover the Phase 3 ScaleToZero API surface (the new desiredState mode + +// idleTimeout) as installed by the chart CRDs and enforced by the validating +// webhook. They are control-plane only, so they run in the agent-off Kind smoke +// harness. The real wake-on-traffic datapath is exercised separately — see the +// Pending spec in scaletozero_datapath_test.go, which needs a KVM node + a real +// Firecracker agent. +var _ = Describe("Imp ScaleToZero API", Label("smoke"), func() { + const vmName = "e2e-sz-vm" + + AfterEach(func() { + _, _ = utils.Run(exec.Command("kubectl", "delete", "impvm", vmName, "-n", "default", "--ignore-not-found")) + }) + + scaleToZeroManifest := func(idleTimeout string) string { + idle := "" + if idleTimeout != "" { + idle = "\n idleTimeout: " + idleTimeout + } + return fmt.Sprintf(` +apiVersion: imp.dev/v1alpha1 +kind: ImpVM +metadata: + name: %s + namespace: default +spec: + classRef: + name: small + image: ghcr.io/syscode-labs/test:latest + desiredState: ScaleToZero%s +`, vmName, idle) + } + + It("rejects a ScaleToZero ImpVM whose idleTimeout is below the 10s floor", func() { + apply := exec.Command("kubectl", "apply", "-f", "-") + apply.Stdin = strings.NewReader(scaleToZeroManifest("5s")) + out, err := utils.Run(apply) + Expect(err).To(HaveOccurred(), "sub-floor idleTimeout should be rejected by the webhook") + Expect(out).To(ContainSubstring("idleTimeout must be at least 10s")) + }) + + It("admits a ScaleToZero ImpVM and returns the experimental warning", func() { + apply := exec.Command("kubectl", "apply", "-f", "-") + apply.Stdin = strings.NewReader(scaleToZeroManifest("30s")) + out, err := utils.Run(apply) + Expect(err).NotTo(HaveOccurred()) + // kubectl prints admission warnings to stderr; utils.Run uses CombinedOutput. + Expect(out).To(ContainSubstring("ScaleToZero is experimental")) + }) +})