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
59 changes: 59 additions & 0 deletions test/e2e/scaletozero_datapath_test.go
Original file line number Diff line number Diff line change
@@ -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")
})
})
81 changes: 81 additions & 0 deletions test/e2e/scaletozero_test.go
Original file line number Diff line number Diff line change
@@ -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"))
})
})
Loading