evetest

package module
v0.0.0-...-0d32dd6 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 15, 2026 License: Apache-2.0 Imports: 76 Imported by: 0

README

EVETest Framework

EVETest (or simply "evetest") is a next-generation integration testing framework for EVE-OS, designed to replace Eden. It enables comprehensive integration testing using virtualization, supports complex network scenarios via a programmable SDN, and provides a simplified developer experience.

Tests are standard Go tests that live in the EVE repository alongside the code they test. Running a test requires a single command:

make evetest NAME=<test-name>

Prerequisites

  • GNU Make
  • Docker (or compatible container runtime)
  • EVE container image already built (make eve from the EVE repo root), or a published lfedge/eve image matching the version you want to test
  • Go 1.25+ (only needed if installing the evetest CLI locally)
  • Nested virtualization support in your CPU/hypervisor (for all-in-one mode)
  • QEMU 11.1.0+ (or 11.0.4+ from the 11.0 stable series) wherever the VM actually runs -- see the QEMU version note below

QEMU version note: older QEMU builds hit issue #2777: on a q35 machine with an AHCI disk, a guest that submits an NCQ command with a zero-length PRDT trips an assertion in ide_dma_cb and the emulator aborts, taking the VM down mid-test. Commit 443e024106 replaces that assertion with proper error handling; it first shipped in v11.1.0 and was backported to v11.0.4, and no 10.2.x release carries it.

Which QEMU that means depends on the provider. libvirt and proxmox run the VM on the hypervisor host, so that host must meet the floor. All-in-one mode (the qemu provider) runs QEMU inside the evetest container instead, and Dockerfile.evetest pins ALPINE_VERSION=3.24, whose qemu package is 11.0.3 -- so that path is currently exposed and cannot be fixed by raising the pin: Alpine's community repository has no stable release at or above the floor (3.22 ships 10.0.0, 3.23 ships 10.1.5 and 3.24 ships 11.0.3, one patch release short of the 11.0.4 backport; only edge, at 11.1.1, clears it, as of September 2026). Closing the gap means a 3.24 package update to qemu 11.0.4 or later, or moving the base image once a stable Alpine ships qemu 11.1.0 or later; re-check the index at that point.

macOS note: the test container is a Linux container and runs normally under Docker Desktop. However, Docker Desktop's Linux VM does not support nested virtualization, so /dev/kvm is never available inside containers on macOS. QEMU falls back to TCG software emulation, which is significantly slower than KVM -- tests may take much longer to complete or time out. On Apple Silicon, EVE must be built for arm64 (make eve produces the correct architecture automatically).

Quick Start

# From the EVE repository root

# 1. Build the EVE image (if testing local changes)
make eve                                 # from repo root

# 2. List available tests and test suites (with their configurable parameters)
make list-tests                          # from evetest/
make evetest-list-tests                  # from repo root

# 3. Run the test
#    Execution can be customized using EVETEST_* environment variables.
#    The NAME variable (or EVETEST_NAME) is mandatory and must reference
#    the name of a test or test suite in the ./tests directory.
EVETEST_LOG_LEVEL=debug make evetest NAME=TestBootstrapWithLastResort  # from repo root or inside evetest/

# 3. (Optional) Pipe through gotestfmt for pretty output
go install github.com/gotesttools/gotestfmt/v2/cmd/gotestfmt@latest
EVETEST_LOG_LEVEL=debug EVETEST_OUTPUT_FORMAT=json make evetest NAME=TestBootstrapWithLastResort | gotestfmt

Writing Tests

Test Structure

Every test follows the same pattern:

func TestMyFeature(test *testing.T) {
    // 1. Initialize the framework and obtain a wrapped test handle for assertions.
    //    Use this handle instead of the original test object.
    //    Ensure resources are released at the end.
    evetestT := evetest.Init(test)
    defer evetest.Close()

    // 2. (Optional) Define configurable parameters.
    //    You can use existing parameters (e.g., HypervisorParameter) or define
    //    new test-specific parameters via TestParameterDefinition.
    //    Parameters can be set through environment variables
    //    (`EVETEST_<param-key>`) or assigned directly within a test suite.
    evetest.DefineTestParameters(
        evetest.HypervisorParameter(),
        evetest.TestParameterDefinition{
            Key:          "MY_BOOL_PARAM",
            DefaultValue: false,
            Description: evetest.TestParameterDescription{
                Summary: "Enable some feature",
                Default: "false",
            },
        },
        evetest.TestParameterDefinition{
            Key:          "MY_ENUM_PARAM",
            DefaultValue: MyEnumValueA,
            Description: evetest.TestParameterDescription{
                Summary:       "Select operating mode",
                Default:       "mode-a",
                AllowedValues: "mode-a|mode-b|mode-c",
            },
        },
    )

    // 3. (Optional) Get parameter values set for this test execution.
    hypervisor := evetest.GetHypervisorParameterValue()
    myParamValue := evetest.GetTestParameter[string]("MY_PARAM")

    // 4. Specify required devices and network model, then call Setup.
    //    Test is skipped if requirements cannot be satisfied.
    evetest.Setup(
        evetest.RequireEdgeDevice{
            Name:           "dev1",
            MinCPUs:        4,
            WithHypervisor: evetest.GetHypervisorParameterValue(),
        },
        evetest.RequireNetworkModel{
            NetworkModel: netmodels.SingleEthWithDHCP,
        },
        evetest.RequireInternetConnectivity{}
    )

    // 5. Obtain a handle to the device and interact with it.
    //    Multiple devices can also be requested and used (e.g., for clustering tests).
    device := evetest.GetEdgeDevice("dev1")
    devUpdates, stopDevWatch := device.WatchDeviceInfo()
    defer stopDevWatch()

    // 6. Build and apply the device configuration.
    devConfig := evetest.NewEdgeDeviceConfig("dev1")
    dhcpNet := devConfig.AddNetwork(evetest.DHCPNetworkConfig{
        NetworkType: evecommon.NetworkType_V4,
    })
    devConfig.AddNetworkAdapter(evetest.NetworkAdapterConfig{
        LogicalLabel:  "eth0",
        PhysicalLabel: "eth0",
        InterfaceName: "eth0",
        NetworkUUID:   dhcpNet,
        Usage:         evecommon.PhyIoMemberUsage_PhyIoUsageMgmtAndApps,
    })
    device.ApplyConfig(devConfig, true, true)

    // 7. (Optional) Insert checkpoints at key points in the test to aid debugging
    //    and inspection. You can stop the test at a checkpoint to examine the
    //    EVE device state and other runtime information through evetest CLI.
    evetest.Checkpoint("config-applied")

    // 8. Perform assertions against EVE API messages published to the controller.
    // Any assertion framework can be used; Gomega is shown as an example.
    t := NewGomegaWithT(evetestT)
    timeout := 3 * time.Minute
    t.Eventually(devUpdates, timeout).Should(Receive(matchers.SatisfyPredicate(
        "Device has applied and reported expected network configuration",
        func(dinfo *eveinfo.ZInfoDevice) bool {
            // Check that the controller-pushed DPC ("zedagent") is active.
            sa := dinfo.GetSystemAdapter()
            return sa != nil && sa.GetCurrentIndex() == 0 &&
                len(sa.GetStatus()) == 1 && sa.GetStatus()[0].GetKey() == "zedagent"
        })))

    // Continue repeating steps 6–8 as needed to test the desired scenario.
}
Init and Close

evetest.Init(t) initializes the framework: it starts the Adam controller, connects to the broker, and launches the gRPC server. It returns a wrapped test handle that should be used for assertions instead of the original testing.T. It must be called first in every test.

evetest.Close() tears down all resources. Always defer it immediately after Init. When running inside a test suite, Close is a no-op for intermediate tests -- resources are reused across the suite and torn down only after the last test.

Test Parameters

Parameters make tests configurable without code changes. They are resolved in order:

  1. Value set by the test suite (if running in a suite)
  2. Environment variable EVETEST_<KEY> (e.g., EVETEST_HYPERVISOR=kvm)
  3. Default value from the parameter definition
evetest.DefineTestParameters(
    evetest.HypervisorParameter(),           // pre-defined: key "HYPERVISOR"
    evetest.FilesystemParameter(),           // pre-defined: key "FILESYSTEM"
    evetest.TPMParameter(),                  // pre-defined: key "TPM"
    evetest.TestParameterDefinition{         // custom parameter
        Key:          "MY_PARAM",
        DefaultValue: true,
        Description: evetest.TestParameterDescription{
            Summary: "Enable some feature",
            Default: "true",
        },
    },
)

hypervisor := evetest.GetHypervisorParameterValue()
myParam := evetest.GetTestParameter[bool]("MY_PARAM")

Custom enum-like types can implement the FromStringer interface (FromString(string) error) to be used as parameter types. See HypervisorParameter for an example.

Test Requirements and Setup

evetest.Setup(requirements...) declares what the test needs. The framework handles all the behind-the-scenes work: building EVE images (live or installer), creating VMs, running the two-step installer boot when requested, configuring networks, waiting for devices to boot and onboard, and establishing tunnels for seamless connectivity between EVE devices, starting and initializing the Adam controller and the test framework itself.

RequireEdgeDevice -- deploy an EVE device VM:

evetest.RequireEdgeDevice{
    Name:              "dev1",          // logical name to reference the device
    MinCPUs:           4,               // default: 4
    MinRAMInMiB:       8192,            // default: 8 GiB
    MinDiskSizeInMiB:  65536,           // default: 64 GiB
    WithHypervisor:    evetest.HypervisorKVM,
    WithFilesystem:    evetest.FilesystemZFS,
    WithTPM:           true,
    DeviceReusePolicy: evetest.CreateFromScratchWithLiveImage,
}

The DeviceReusePolicy controls how existing devices from a previous test (in a suite) are handled. Devices are only reused if they also satisfy the requirements of the next test; those that do not match the next test’s requirements are torn down and not reused.

Policy Behavior
UseAsIs Keep existing state
RebootEdgeDevice Reboot the device
ResetDeviceConfig Clear app settings, preserve network config
ResetDeviceConfigAndReboot Clear settings and reboot
ReonboardEdgeDevice Force re-onboarding
CreateFromScratchWithLiveImage Recreate VM with live image
CreateFromScratchWithInstaller Recreate VM using installer image

RequireNetworkModel -- configure the SDN network environment:

evetest.RequireNetworkModel{
    NetworkModel: netmodels.SingleEthWithDHCP,
}

Network models are declarative descriptions of the network topology: ports, bridges, VLANs, DHCP/DNS servers, firewalls, proxies, and more. See the evetest/tests/netmodels/ directory for examples, and sdn/README.md for the full network model reference.

RequireInternetConnectivity -- verify and require Internet access:

evetest.RequireInternetConnectivity{
    RequireIPv6: true,  // optional: also require IPv6
}

RequireIPv6OnlyRegistryMirrors -- for IPv6-only devices, only use configured registry mirror addresses that are themselves IPv6 (an IPv4-only mirror is unreachable to such a device). A registry with no IPv6 mirror configured is simply not mirrored for that app (falls back to the real, un-mirrored registry) rather than failing or skipping the test:

evetest.RequireIPv6OnlyRegistryMirrors{}

If any requirement cannot be satisfied, the test is marked as skipped.

Building and Applying Configuration

After Setup returns, all required devices are powered on and onboarded. Next, build the device configuration programmatically:

// Add networks
dhcpNet := devConfig.AddNetwork(evetest.DHCPNetworkConfig{...})
staticNet := devConfig.AddNetwork(evetest.StaticNetworkConfig{...})

// Add network adapters
devConfig.AddNetworkAdapter(evetest.NetworkAdapterConfig{...})

// Set device-wide config properties
devConfig.SetConfigProperties(cfgProps)

// Apply and wait for the device to fetch and confirm the config.
// waitUntilFetched=true: wait for EVE to request the config from the controller.
// waitUntilConfirmed=true: also wait for EVE to report LastProcessedConfig >= configTimestamp.
device := evetest.GetEdgeDevice("dev1")
device.ApplyConfig(devConfig, true, true)

You can modify and re-apply the configuration multiple times during a test to verify how EVE reacts to configuration changes.

Interacting with Devices

The EdgeDevice object provides methods for interacting with the running EVE device:

device := evetest.GetEdgeDevice("dev1")

// Run commands via SSH
stdout, stderr, err := device.RunShellScript("uptime", timeout, stdoutWatchdogTimeout)

// Inspect the device filesystem (prefer these over ad-hoc shell scripts)
data, err := device.ReadFile("/persist/status/nodeagent/global.json")
// FileExists covers regular files and fails the test if the check cannot run;
// PathExists accepts any node type and returns the error instead, so it can be
// retried inside Eventually while the device is still converging.
hasReason := device.FileExists("/persist/reboot-reason")
hasVolumeDir, err := device.PathExists("/persist/vault/volumes")
// Sorted entries; a directory EVE has not created yet yields an empty list.
names, err := device.ListDirEntries("/run/hypervisor/kvm")
device.WriteFile("/persist/marker", []byte("x"))
device.DeleteFile("/persist/marker")
device.SyncDisks() // flush filesystem caches, e.g. before PowerOff

// Change the device's hardware at runtime through the hypervisor; the test must
// declare RequireCapabilities{CAPABILITY_QMP} (qemu and proxmox providers).
device.AttachUSBStorage("flash1", 16<<20) // hot-plug a blank USB flash drive, serial "flash1"
usbDevices, err := device.ListUSBDevices() // what EVE's kernel enumerates under /sys/bus/usb
drive := device.WaitForUSBDevice(t, "flash1", time.Minute, 2*time.Second) // poll until enumerated, fail the test on timeout
appUSBDevices, err := device.ListUSBDevicesInsideApp(appUUID, appAuth) // same, inside an app
device.DetachUSBStorage("flash1")
hubPort := device.AttachUSBHub("hub1", "") // hot-plug a QEMU USB hub into a root port; "" = no parent hub
device.AttachUSBStorageBehind("hub1", "flash2", 16<<20) // a drive into the lowest free port of that hub
device.DetachUSBHub("hub1") // once everything plugged into it is detached
ret := device.ExecuteQMP("query-status", nil) // any other QMP command; raw JSON "return"

// Read EVE's internal published state (pubsub)
var dpcl pillartypes.DevicePortConfigList
err := evetest.ReadPublication(device, "nim", true, "global", &dpcl)

// Read all publications of a type
items, err := evetest.ReadAllPublications[pillartypes.AppInstanceStatus](
    device, "zedmanager", false)

// Get the latest device info/metrics (or nil if not yet received)
info := device.GetDeviceInfo()
metrics := device.GetDeviceMetrics()

// Watch for info/metrics updates in real-time
updates, stop := device.WatchDeviceInfo()
defer stop()
for msg := range updates { ... }

// Apply configuration changes
device.ApplyConfig(newConfig, true, true)

// Reboot (pass true to wait until the device comes back up)
device.SoftReboot(true)
device.HardReboot(true)

// Upgrade to another EVE version and hypervisor. The optional last argument
// replaces the default wait, which is sized for an ordinary base-OS upgrade.
device.UpgradeEVE(version, evetest.HypervisorKubevirt, evetest.BaseOSDatastoreHTTP,
    true, false, 45*time.Minute)

// Everything the device has written to its serial console so far, including
// what ran before pillar started, such as storage-init
console, err := device.ConsoleOutput()

// Skip the end-of-test check that the device rebooted as often as expected,
// for a test whose reboot count is unknowable, such as one that cuts power
device.DisableRebootAccounting("power cut mid-swap")
Checkpoints

Insert named checkpoints to create pause points for interactive debugging:

evetest.Checkpoint("setup-done")
// ... more test logic ...
evetest.Checkpoint("config-applied")
// ... more test logic ...
evetest.Checkpoint("another-import-point-of-tested-scenario")

When EVETEST_PAUSE_ON_CHECKPOINT matches a checkpoint name, the test pauses there. Use the CLI to inspect state, then run evetest continue to resume.

Guidelines
  • Document the test in its function comment: state the objective, the network model used and why, the device configuration, the test phases (numbered), and any parameters. Look at existing fully-implemented tests for the expected structure.

  • Focus on important use cases, not on maximizing code-line coverage. A test that exercises a realistic end-to-end scenario is more valuable than one that reaches a high line count by poking at internal helpers.

  • Assert against the EVE API (device info, metrics, publications) rather than implementation details that may change between EVE versions. Avoid SSH-ing into EVE to read internal state files. The exception is Linux-standard files that do not change between EVE versions (e.g., /etc/resolv.conf).

  • Shorten timers by setting config properties (e.g., timer.*) to smaller values wherever EVE allows it, to reduce overall test duration. Be aware that some timers have a hard minimum floor that cannot be overridden.

  • Use Eventually for all device-state assertions. Everything in EVE is processed asynchronously. For example, an acknowledgment from Adam that config was received does not mean it has been fully applied — it still has to trickle through multiple microservices. Similarly, an app reaching the ONLINE state does not mean it has fully booted, received an IP address, or is reachable over SSH.

  • Prefer channel-based watches (WatchDeviceInfo, WatchAppInfo, WatchNetworkInstanceInfo, etc.) over periodic polling with short intervals. Each polling call reloads all previously published messages of that type from Adam, which is both slow and redundant; channel-based watches deliver only new messages as they arrive.

  • For set/list comparisons use pkg/pillar/utils/generics (e.g., generics.EqualSets); for network address comparisons use pkg/pillar/utils/netutils/ip.go.

  • Reuse existing network models from evetest/netmodels/. When adding a new model, keep it general-purpose — describe it in terms of topology, not the specific test that first needed it, so it can be reused across scenarios.

  • Order tests in a suite to maximize device reuse: group tests that share device and network requirements so the framework can reuse existing VMs instead of recreating them between tests.

  • Place a checkpoint after every significant configuration or state change. This creates a named pause point targetable with EVETEST_PAUSE_ON_CHECKPOINT for interactive inspection.

  • Use parameters instead of copy-pasting tests. When two variants differ only in a few values/steps, define a TestParameterDefinition and register suite variants with different TestParameterValue entries instead of duplicating the entire test body.

  • Use evetest.Logger() for all log output inside tests; its formatting is consistent with the rest of the framework.

  • Register the new test in the suite. After writing a test function, add it to the appropriate RunTestSuite call in testsuite_test.go in the same package.

  • Reuse existing package-level helpers before writing new ones. Each test package has shared helpers for common patterns — for example the networking package has getDevicePort, getCurrentDPC, appHasError, niHasError. Check the other _test.go files in the package before duplicating logic. A helper that says nothing about the subject under test — reading a file, listing a directory, checking a path, flushing caches — belongs on EdgeDevice in evetest/edgedevice.go instead, where every package can reuse it.

  • Keep helpers out of the test files. Small packages put all shared helpers in helpers_test.go. When a package accumulates more than that file can carry, split it into files whose names end in _helpers_test.go, so that a file named for a test contains only that test. Name each helper file for the state it observes — not for the test that first needed it — and record the layout in the package comment in that package's testsuite_test.go; see tests/apps/ for a worked example. Check that comment before adding a helper.

  • Do not mutate shared global state. If a test needs to modify a package-level variable (e.g. a network model defined in evetest/netmodels/), operate on a deep copy, or make sure to revert the change before the test returns. Unreverted mutations will silently affect subsequent tests in the same suite.

  • Use Gomega for assertions. The framework does not impose an assertion library, but Gomega is recommended — all existing tests use it and the evetest/matchers/ package provides evetest-specific Gomega matchers. In particular, use matchers.SatisfyPredicate when passing a predicate to Eventually: it attaches a human-readable description to the predicate and supports .StopIf(fn) to fail fast on unrecoverable errors instead of waiting out the full timeout.

Test Suites

Test suites group multiple tests for sequential execution with resource reuse. When tests in a suite share similar requirements, the framework reuses existing VMs instead of recreating them for each test.

func TestBootstrapSuite(test *testing.T) {
    evetest.Init(test)
    defer evetest.Close()

    // Suite-wide parameters (override individual test defaults)
    evetest.DefineTestParameters(
        evetest.HypervisorParameter(),
    )

    evetest.RunTestSuite(
        evetest.TestCase{
            Test: TestBootstrapWithLastResort,
            Variants: []evetest.TestVariant{
                {
                    Name: "LastResortDisabled",
                    Parameters: []evetest.TestParameterValue{
                        {Key: "LAST_RESORT_ENABLED", Value: false},
                    },
                },
                {
                    Name: "LastResortEnabled",
                    Parameters: []evetest.TestParameterValue{
                        {Key: "LAST_RESORT_ENABLED", Value: true},
                    },
                },
            },
        },
        evetest.TestCase{
            Test: TestDHCPIPv4Only,  // no variants: runs once with defaults
        },
    )
}

Each variant runs as a Go subtest (t.Run). The EVETEST_SUITE_MAX_FAILURES variable controls early termination: 1 (default) aborts after the first failure, -1 runs all tests regardless of failures.

Run a suite like any other test:

make evetest NAME=TestBootstrapSuite
Tagging Tests and Suites

A test or suite can be tagged through an option of its Init call:

evetest.Init(test, evetest.WithTags(evetest.TagHeavyTest))

list-tests (make list-tests) shows the tags next to the name. make list-test-suites prints the same <tags> after each suite name, for CI to read. CI schedules whole suites, so it only reads the tags of suites; tags of individual tests are informational. WithTags does nothing at run time: the tags are read from the source, so pass constants from the evetest package directly.

TagHeavyTest marks a test or suite that needs a powerful host. The nightly and PR workflows run suites tagged so only on runners labelled evetest-runner; all other suites may also use runners labelled only evetest-runner-light.

Rerunning Only Failed Subtests

EVETEST_RESTART_ONLY_FAILED=true reruns a suite while skipping every subtest that already passed in a previous run, reporting it as SKIPPED instead of re-executing it (a skip does not count as a failure). This is useful both locally, to quickly tell whether a failure is deterministic or a fluke without waiting on the whole suite again, and in CI, to quickly confirm whether a failure in an open PR is real by rerunning just the failed subtests.

The feature requires EVETEST_COLLECT_ARTIFACTS -- it works by reading gotest.txt (or gotest.json, if the previous run used EVETEST_OUTPUT_FORMAT=json) from a previous run's artifact directory. Since every artifact directory is named <test-name>-<timestamp> (e.g. TestBootstrapSuite-2026-08-07_08-16-29), the previous run is simply the newest directory of the same suite that predates the current run; runs of other suites are ignored. This also means that a run performed elsewhere (e.g. a previous CI attempt) can be consulted just by unpacking its artifact directory, under its original name, into the artifacts directory. EVETEST_RESTART_ONLY_FAILED never applies to a standalone test run outside of a suite. A previous run made with EVETEST_OUTPUT_FORMAT=quiet cannot be used, since that mode records no per-test results at all.

EVETEST_COLLECT_ARTIFACTS=/tmp/evetest-artifacts \
    make evetest NAME=TestBootstrapSuite
# ... some subtest fails ...

EVETEST_COLLECT_ARTIFACTS=/tmp/evetest-artifacts \
EVETEST_RESTART_ONLY_FAILED=true \
    make evetest NAME=TestBootstrapSuite
# Subtests that passed last time are reported as SKIPPED right away; only the
# previously-failed (or previously-unreached) subtests actually run.

Running Tests

Basic Usage
# Run a single test
make evetest NAME=TestBootstrapWithLastResort

# Run a test suite
make evetest NAME=TestBootstrapSuite

# With debug logging and formatted output
EVETEST_LOG_LEVEL=debug make evetest NAME=TestBootstrapSuite | gotestfmt

# With a specific EVE version
EVETEST_EVE_VERSION=0.0.0-my-branch-abc123 \
    make evetest NAME=TestDHCPIPv4Only

# Collect artifacts (logs, Adam DB snapshot, collect-info from each device on failure, etc.)
EVETEST_COLLECT_ARTIFACTS=/tmp/evetest-artifacts \
    make evetest NAME=TestDHCPIPv4Only
Testing a Local EVE Build

A developer iterating on EVE can point evetest at their own make live output instead of pulling or building an EVE container image:

make live                                               # build EVE locally
EVETEST_EVE_LIVE_IMAGE=true make evetest NAME=<TestName>

Two independent settings. Which EVE build runs and how its bits get delivered are separate questions:

setting values
which build EVETEST_EVE_VERSION a version (16.0.0-lts), or unset
how it is delivered EVETEST_EVE_LIVE_IMAGE true = the artifacts make live wrote; unset/false = an EVE container image

A local build is not a transport: make eve produces a local container image, and the harness pushes it to the broker when the broker does not have it. So EVETEST_EVE_LIVE_IMAGE says only how, never which — it is a plain boolean and takes no path.

With the live transport on, the version selects the build directory under EVETEST_EVE_DIST_DIR (set automatically by make evetest):

  • EVETEST_EVE_VERSION unset — the newest build, via dist/<arch>/current; the run reports whatever version that turns out to be. Note the symlink is arch-scoped (dist/amd64/current, not dist/current).
  • EVETEST_EVE_VERSION=<v> — dist/<arch>/<v>/. If that version is not built here, the run fails rather than quietly fetching it from a registry: you asked for a specific build and for the live transport, and silently delivering different bits is the kind of thing that costs an afternoon to notice.
  • A version pinned by the test itself (RequireEdgeDevice.WithEVEVersion, e.g. TestEVEUpgrade's pre-upgrade version) names a released build and always comes from a container image, whatever the transport setting.

The harness content-hashes the image, then tells the broker both the hash and where the files are (live.qcow2, installer/config.img, installer/firmware/*), and the broker picks how to get them:

  • It can read those paths itself -- all-in-one mode, or a broker you started by hand on your own machine. It installs the template straight from the dist directory, and nothing is uploaded at all. Whether the paths are readable is decided by the broker, by looking; the harness does not guess from its deployment mode.
  • It cannot (the usual distributed setup, where the broker is a different machine). It reports the image as missing and the harness uploads it as a tar, exactly as before.

Either way the upload/read happens only when the broker does not already hold that hash, and the template is then installed through the machinery described in The EVE Image Template Cache. The version reported for the run is the resolved build directory's name (e.g. 0.0.0-my-branch-abc123), which is the authority on what was actually delivered.

A local read is only ever used when the file's size and its sha256 match what the harness declared, so a broker can never install different content under a hash another run will later ask for -- the same check the upload path applies to received bytes.

This is dramatically faster than the container path: there is no container build and no multi-gigabyte pull. Rebuilding with make live produces a qcow2 with different content and therefore a new hash, so the next run performs exactly one new upload -- nothing is re-uploaded until the image actually changes.

Upgrading to a local build

TestEVEUpgrade boots INITIAL_EVE_VERSION and upgrades to EVE_VERSION, and the two axes make both useful shapes fall out without any special-casing:

# A released version upgraded to another released version
EVETEST_INITIAL_EVE_VERSION=16.0.0-lts EVETEST_EVE_VERSION=16.1.0-lts \
    make evetest NAME=TestEVEUpgrade

# A released version upgraded to your working tree
make live
EVETEST_INITIAL_EVE_VERSION=16.0.0-lts EVETEST_EVE_LIVE_IMAGE=true \
    make evetest NAME=TestEVEUpgrade

The pre-upgrade device pins INITIAL_EVE_VERSION, so it always comes from a container image — a released build is the point of that field. Only the target uses the live transport, and it needs a different artifact than a fresh device does: an upgrade installs a base OS image, so the harness serves the build's own installer/rootfs.img from its HTTP image server and takes the version EVE will report from installer/eve_version. No container is pulled and the broker is not involved at all, since the rootfs goes straight from the harness to the device.

Two constraints to be aware of:

  • Installer-based tests cannot use this path. A live qcow2 cannot produce an installer flow, so a test that also requests an installer (CreateFromScratchWithInstaller) fails immediately with a clear error instead of silently falling back to the container path.
  • The broker must advertise CAPABILITY_LOCAL_LIVE_IMAGE. All three providers do today; a broker too old to support the feature, or a future provider that still builds images per device (see The EVE Image Template Cache), fails the test with a clear error rather than quietly falling back and testing a different EVE build than the one requested.

See Essential Variables for the full reference on EVETEST_EVE_LIVE_IMAGE, EVETEST_EVE_DIST_DIR, and EVETEST_EVE_FIRMWARE_DIR.

Code Coverage

When EVE is built with COVER=y, the zedbox binary is instrumented for Go coverage. Setting EVETEST_COLLECT_COVERAGE=true (together with EVETEST_COLLECT_ARTIFACTS) tells the framework to collect coverage data:

  • Before every device reboot (HardReboot, SoftReboot, RequestReboot, UpgradeEVE, ExpectAdditionalReboots, and the CLI evetest eve hard-reboot / evetest eve soft-reboot). A reverted upgrade's second reboot has no such hook, since EVE (not evetest) decides when to revert.
  • At test completion (inside Close()).

For each collection the framework:

  1. Sends SIGUSR2 to zedbox, which flushes in-memory counters to /persist/coverage without terminating the process.
  2. Polls for new .covcounters files (up to 30 s).
  3. SCP-copies the files to ${EVETEST_COLLECT_ARTIFACTS}/<test-name>/coverage/<device-name>/.

Because Go names counter files as covcounters.<hash>.<pid>.<nanotime>, all collections for a device accumulate in the same directory without conflicts.

Building EVE with coverage
# Build coverage-instrumented pillar first (required):
make COVER=y pkg/pillar

# Then build the eve image.
make COVER=y eve
Running with coverage collection
EVETEST_COLLECT_ARTIFACTS=/tmp/evetest-artifacts \
EVETEST_COLLECT_COVERAGE=true \
    make evetest NAME=TestDHCPIPv4Only

Coverage files land in ${EVETEST_COLLECT_ARTIFACTS}/TestDHCPIPv4Only-<timestamp>/coverage/edge-dev/.

Merging and viewing results

After the test, merge all counter files with the Go toolchain and convert to a human-readable HTML report:

COVDIR=${EVETEST_COLLECT_ARTIFACTS}/<testname>-<timestamp>/coverage/<device-name>

# Merge all counter files into a single dataset
mkdir -p /tmp/merged-coverage
go tool covdata merge -i "$COVDIR" -o /tmp/merged-coverage

# Show per-package coverage percentages
go tool covdata percent -i /tmp/merged-coverage

# Convert to the legacy text profile format
go tool covdata textfmt -i /tmp/merged-coverage -o /tmp/coverage.txt

# Show overall total coverage
go tool cover -func=/tmp/coverage.txt | tail -1

# Open an HTML report in the browser
go tool cover -html=/tmp/coverage.txt

To merge across all sub-tests and all devices, use find to collect every per-device directory that lives under any coverage/ tree:

mkdir -p /tmp/merged-coverage
go tool covdata merge \
    -i "$(find ${EVETEST_COLLECT_ARTIFACTS} -path '*/coverage/*' -type d | paste -sd,)" \
    -o /tmp/merged-coverage

The -path '*/coverage/*' pattern matches directories one level below any coverage/ directory, which are the per-device subdirectories.

Debugging with Pause

Pause on failure -- when a test fails, the environment stays up for inspection:

EVETEST_PAUSE_ON_FAILURE=true make evetest NAME=TestDHCPIPv4Only

# In another terminal:
evetest status            # see what happened
evetest eve ssh           # SSH into the EVE device
evetest eve logs -f       # tail logs
evetest continue          # let the test tear down
# or
evetest exit              # tear down and exit immediately

Pause on checkpoint -- stop at a specific point in the test:

EVETEST_PAUSE_ON_CHECKPOINT=config-applied \
    make evetest NAME=TestBootstrapWithLastResort

# In another terminal:
evetest eve config        # inspect the submitted config
evetest eve info          # check device status
evetest continue          # resume the test
# or
evetest continue --until setup-done  # resume until a different checkpoint

Command Line Interface

The CLI provides runtime interaction with a running evetest instance. It communicates via gRPC with the evetest container.

Install it on your host:

make install-cli

Or use it from inside the container (docker exec -it evetest-<API-port> bash).

Running Several Tests in Parallel

make evetest accepts several comma-separated names, or none at all, which runs every test suite. Each test then runs as its own instance (container) with its own API port, and EVETEST_PARALLEL limits how many run at the same time (default 1, i.e. one after another):

EVETEST_PARALLEL=3 make evetest NAME=TestA,TestB,TestC
EVETEST_PARALLEL=4 make evetest                  # all test suites

A container started by make (the orchestrator) starts and watches the instances. Its output depends on EVETEST_UI:

EVETEST_UI Output
dashboard One screen with a table of the instances and a full-screen output view of the selected one. Default on a terminal
tmux One tmux session, the instances in tiled panes of a single window
prefixed Output of all instances interleaved, each line prefixed with the test name
plain Only starts, results and, for a failed test, the end of its output. Default when not on a terminal, e.g. when started by a script or an AI agent, or with nohup on a remote host

The full output of every test is also written to ~/.evetest/runs/<run-id>/<test>.log (and the usual artifacts are collected as with a single test). The directories of earlier runs, whose orchestrator is no longer running and which nothing has written to for 5 minutes, are removed when a new run starts, so copy the logs you want to keep, or collect artifacts. A summary table closes the run, and make exits non-zero if any test failed. With EVETEST_FAIL_FAST=true the first failure cancels the tests still running or queued.

Dashboard UI

EVETEST_UI=dashboard, the default on a terminal. A single screen, not a terminal multiplexer: a table with one row per test (state, time, last line of output), and a full-screen scrollable view of the output of the selected test. A running test shows what it is doing: paused (checkpoint ...) or paused (test failed) when paused. Tests are controlled from the same screen, with no prefix key. It stays open after the last test finishes, until q, and then the summary is left in the terminal. A rerun reads the files under tests/, netmodels/ and matchers/ afresh (they are mounted from the checkout), so edits made since the previous run are picked up. The framework code (the evetest root package, broker/, ...) and the SDN are baked into the images, so to pick up changes to them, build the images of the new sources first, unless they are published (make -C evetest build-container and build-sdn-container) and enter their versions (make -C evetest version and sdn-version print them) in the rerun prompt. A rerun pulls images which are not local, like make evetest, and is refused if that fails; it does not build.

Key Action
up / down / j / k, 1-9 Select a test (scroll, in the output view)
enter / esc Show the output of the selected test / back to the table
pgup / pgdn, g / G Scroll the output; top / follow the end
s Open an ssh session to the EVE device of the selected test; a test with several devices asks which one first (leave the session with exit; a failed ssh waits for Enter so its error can be read)
c Resume the selected test if it is paused (evetest continue)
x Stop the selected test gracefully; a queued test is cancelled instead, so that it does not run (r queues it again)
r Rerun the selected test, once it is done (passed, failed or stopped). It first asks which evetest and SDN image versions to use (enter keeps those of the previous run, esc cancels). The test then goes back to the queue; its output continues in the output view below a divider, and the log of each run is kept in a file of its own (<test>.2.log, ...)
q / Ctrl-c Stop all tests (asks first); pressed again while stopping, kills them

Unlike the tmux UI below, the dashboard has no panes, so a test cannot read the keyboard directly, and there is no scrollback outside the output view; on the other hand it needs nothing but the orchestrator, and shows every test's state at a glance, also for many tests.

tmux UI

EVETEST_UI=tmux. Each instance runs in the foreground of its own pane, so it takes keyboard input as when run alone (e.g. to resume a paused test), has scrollback and can be zoomed. The pane border shows the test, its API port and its state. When a test fails, its pane moves to a window of its own (FAILED <test>), so its output stays on screen while the remaining tests continue. The pane of a test which passed closes right away (its output is in its log file). The first window (orchestrator) lists what happened and, at the end, the summary with a reminder of where to find the output of each test (the FAILED <test> windows, the log files). The status line shows how many tests are running, queued, passed and failed. The session stays open after the last test finishes, until it is closed.

The tmux prefix key is Ctrl-a (change it with EVETEST_TMUX_PREFIX, e.g. C-b), which differs from tmux's default so as not to clash with a tmux you run the make in. Mouse support is on (click a pane to focus it). Useful keys, after the prefix:

Keys Action
Q Close the session; asks first while tests are still running, which are then stopped gracefully
X Stop all tests gracefully
arrow keys / o / ; Select the pane above, below, left or right / the next pane / the previous pane (or click a pane); the selected pane gets your keystrokes
q and a number Show the pane numbers, then select that pane
e / % Open a shell for the evetest CLI below / to the right of the selected test (exit closes it)
z Zoom the current pane in or out
n / p / 0-9 Next / previous / numbered window (the orchestrator and failed tests)
[ Scrollback and search of the pane, with PgUp/PgDn/arrows and / (q leaves); the mouse wheel scrolls a pane as well

Detaching is disabled: closing the session stops the run.

A test's pane runs the test, so it cannot take commands. To use the evetest CLI for a test, select its pane and press Ctrl-a e: a shell opens below it (Ctrl-a % opens it to the right instead), set up for that test. evetest status, evetest eve ssh and the like work in it without -i (add -d <device> when the test has several EVE devices). Its border says <test>: evetest CLI shell, so it is not mistaken for the test. Type exit to close it.

Other UIs and stopping instances

With the prefixed and plain UIs, Ctrl-C stops all instances gracefully (they clean up their VMs); a second Ctrl-C kills them. They do not forward keyboard input to the instances, so a paused test is resumed with evetest -i <test> continue (or exit).

With any UI, instances which outlive the orchestrator can be stopped with evetest stop --all. A test stopped from outside is reported as cancelled (its container exits with code 143 or 130).

You can also interact with any instance with the CLI from another terminal as described below, for example EVETEST_PAUSE_ON_FAILURE=true combined with evetest -i TestB eve ssh.

Running Several Instances Manually

Each single-test make evetest runs one instance (container) on its own API port. To run several at once by hand, start each in its own terminal with a different EVETEST_API_PORT:

make evetest NAME=networking/dpc                          # port 50021 (default)
EVETEST_API_PORT=50022 make evetest NAME=apps/lps         # port 50022

The CLI finds running instances by itself, there is no need to know their ports:

evetest instances         # list running instances (alias: ps); * marks this terminal's choice
evetest use lps           # use this instance in the current terminal (name, unique part of it,
                          # port or number from the list); without argument: choose from a menu
evetest status            # talks to the chosen instance
evetest -i dpc eve ssh    # one-off override with --instance / -i (or EVETEST_INSTANCE=dpc)
evetest logs -f           # follow the container output of the instance
evetest stop              # stop the instance gracefully (--all: every instance)
evetest use --clear       # forget the choice

The CLI picks the instance in this order: --instance, EVETEST_INSTANCE, an explicitly set EVETEST_API_PORT/EVETEST_API_ADDRESS, the instance chosen with evetest use in this terminal, the only running instance, and finally, if several are running, an interactive menu (whose choice is remembered like evetest use).

The choice made by evetest use is remembered per terminal session (a window or tmux pane), not globally. It is not remembered when there is no terminal (scripts); use --instance there, or set the variable in the shell with eval "$(evetest use --env <instance>)".

Instances register themselves in ~/.evetest/instances/ while running. Remote use (EVETEST_API_ADDRESS, below) is not affected: the registry only describes instances on the local host.

The CLI can also be used from a remote machine (e.g. a developer's laptop while the test runs inside a self-hosted CI runner). Set EVETEST_API_ADDRESS to the IP or hostname of the machine running the evetest container, and EVETEST_API_PORT if a non-default port is used:

export EVETEST_API_ADDRESS=192.168.1.50   # IP of the machine running evetest
export EVETEST_API_PORT=50021             # omit if using the default

evetest status
evetest eve ssh
evetest eve console

All CLI commands work in remote mode, including interactive ones (ssh, scp, console, portfwd), which are tunneled over the gRPC connection, thus no additional ports need to be reachable.

Shell Auto-Completion
# bash
evetest completion bash > evetest-completion.bash
sudo mv evetest-completion.bash /etc/bash_completion.d/evetest

# zsh
evetest completion zsh > ~/.zsh/completions/_evetest

# fish
evetest completion fish > ~/.config/fish/completions/evetest.fish
Test Control
evetest status                          # current test status
evetest continue                        # resume a paused test
evetest continue --until <checkpoint>   # resume until a specific checkpoint
evetest exit                            # tear down and exit
EVE Device Commands

All EVE commands accept --devicename <name> (defaults to the first device).

evetest eve info [-f]                   # device info (follow with -f)
evetest eve metrics [-f]                # device metrics
evetest eve logs [-f]                   # device logs
evetest eve config                      # get current device config
evetest eve console-output              # full console/boot log

evetest eve app-info <app> [-f]         # application info
evetest eve app-metrics <app> [-f]      # application metrics
evetest eve app-logs <app> [-f]         # application logs
evetest eve flow-logs <app> [-f]        # application flow logs
evetest eve ni-info <ni> [-f]           # network instance info
evetest eve ni-metrics <ni> [-f]        # network instance metrics

evetest eve ssh [command...]            # SSH into EVE device
evetest eve scp --from-device src dst   # copy files from EVE
evetest eve scp --to-device src dst     # copy files to EVE
evetest eve console                     # enter interactive console (telnet)
evetest eve collect-info                # collect diagnostic tarball
evetest eve kubectl [kubectl-args...]   # interact with K3s running on EVE-k

evetest eve hard-reboot                 # hard reboot
evetest eve soft-reboot                 # soft reboot
SDN Commands
evetest sdn status                      # SDN status and config errors
evetest sdn net-model                   # current network model
evetest sdn graph                       # config graph (Graphviz dot)
evetest sdn logs                        # stream SDN logs
evetest sdn ssh [command...]            # SSH into SDN VM
Cluster Commands
evetest cluster info [-f]               # Kubernetes cluster info
evetest cluster metrics [-f]            # Kubernetes cluster metrics
Broker Commands

These talk to the evetest-broker at EVETEST_BROKER_ADDRESS:EVETEST_BROKER_PORT directly and need no running test. CI uses them to stage the EVE image built by a workflow run on the broker once per host, no matter how many suite jobs need it:

# Exits 0 and prints a ticket if this job has to provide the image; exits 10 if the broker
# has it. While another job is uploading it, waits, and takes over if that job fails.
TICKET=$(evetest broker image claim-upload --repo evebuild/pr --version <version> --hypervisor kvm)
# A `docker save` archive tagged <repo>:<version>-kvm-amd64; the ticket says which image it is.
evetest broker image upload --ticket "$TICKET" eve-kvm-amd64-generic.tar
evetest broker image release-claim --ticket "$TICKET"   # give the claim up after a failed download

A claim lapses after 60 minutes if the upload does not start (time to fetch the image) and after 10 minutes without upload progress once it runs, so a job that died cannot block the others for long. A test that needs the image on the runner's own Docker (an EVE upgrade target) loads it from the broker when it cannot pull it.

Configuration

All configuration is done through environment variables; there are no configuration files. The framework provides sensible defaults; you only need to set variables when you want non-default behavior.

Essential Variables
Variable Description Default
EVETEST_NAME Test or suite name to run (required) --
EVETEST_OUTPUT_FORMAT go test output format: json (machine-readable, for gotestfmt) or quiet (compact, no -v); default is verbose (-v). Do not combine quiet with EVETEST_PAUSE_ON_FAILURE or EVETEST_PAUSE_ON_CHECKPOINT — without -v, go test buffers all output until the test completes, so a pause appears frozen with no visible output. --
EVETEST_EVE_VERSION EVE version to test current repo HEAD
EVETEST_EVE_LIVE_IMAGE How EVE's bits reach a device: true delivers the artifacts make live wrote under EVETEST_EVE_DIST_DIR, unset/false uses an EVE container image. A boolean only -- which build to run is EVETEST_EVE_VERSION's business, so this takes no path, and a non-boolean value is an error. See Testing a Local EVE Build false
EVETEST_EVE_DIST_DIR EVE build output directory whose <arch>/<version>/ subdirectories (and the <arch>/current symlink) hold the local builds. Must be an absolute path (the harness runs inside a container). Set automatically by make evetest when a local dist/ directory exists --
EVETEST_EVE_FIRMWARE_DIR Overrides firmware discovery for a local live image, which otherwise looks for OVMF*.fd in installer/firmware next to the resolved qcow2 --
EVETEST_PREFERRED_ARCH Preferred CPU architecture (amd64, arm64) amd64
EVETEST_LOG_LEVEL Framework log level (debug, info, warn) info
EVETEST_COLOR_OUTPUT Colorize framework log output with ANSI escape codes (true/false) auto: enabled only when stdout is a terminal, disabled when piped or redirected
EVETEST_COLLECT_ARTIFACTS Host path for artifacts (logs, collect-info) --
EVETEST_COLLECT_COVERAGE Collect Go coverage profiles (requires EVETEST_COLLECT_ARTIFACTS and EVE built with COVER=y) false
EVETEST_REGISTRY_MIRROR_DOCKER Pull-through cache URL(s) for docker.io — one or more comma-separated [scheme://]host:port[/path] (IPv6 hosts bracketed, e.g. http://[fd11::5]:5000); see RequireIPv6OnlyRegistryMirrors --
EVETEST_REGISTRY_MIRROR_GHCR Pull-through cache URL(s) for ghcr.io --
EVETEST_REGISTRY_MIRROR_QUAY Pull-through cache URL(s) for quay.io --
EVETEST_REGISTRY_MIRROR_K8S Pull-through cache URL(s) for registry.k8s.io --
EVETEST_REGISTRY_MIRROR_GCR Pull-through cache URL(s) for gcr.io --
EVETEST_REGISTRY_MIRROR_MCR Pull-through cache URL(s) for mcr.microsoft.com --
Debugging Variables
Variable Description Default
EVETEST_PAUSE_ON_FAILURE Pause when a test fails false
EVETEST_PAUSE_ON_CHECKPOINT Pause at the named checkpoint --
EVETEST_PAUSE_LIMIT Maximum time in seconds to stay paused at a checkpoint or on a failure; after that the test resumes as if evetest continue was run (0 = wait indefinitely) 0
EVETEST_SUITE_MAX_FAILURES Max failures before aborting suite (-1 = unlimited) 1
EVETEST_RESTART_ONLY_FAILED Skip suite subtests that already passed in a previous run (requires EVETEST_COLLECT_ARTIFACTS); see Rerunning Only Failed Subtests false
EVETEST_CONTROLLER_FAULTS Run the controller behind a proxy which a test can tell to answer, delay or drop selected device requests (ArmControllerFault). Off by default, except that tests which arm faults ask for it themselves (evetest.WithControllerFaults()), so nothing needs setting to run them; an explicit true turns it on for every test and false turns it off for all, which makes the fault tests skip. With no fault armed the proxy forwards everything unchanged, but it terminates TLS and re-originates the request, so it stays out of the path unless a test needs it. false
EVETEST_PARALLEL Maximum number of instances running at the same time when make evetest runs several tests; see Running Several Tests in Parallel 1
EVETEST_FAIL_FAST In a multi-test run, cancel the other tests once one fails false
EVETEST_UI Output mode of a multi-test run: tmux, dashboard, prefixed or plain dashboard on a terminal, else plain
EVETEST_TMUX_PREFIX Prefix key of the tmux session of the tmux UI C-a
Deployment Variables
Variable Description Default
EVETEST_BROKER_ADDRESS Broker IP (unset = embedded broker) --
EVETEST_BROKER_PORT Broker gRPC port 50221
EVETEST_BROKER_DEVICE_PROVIDER qemu, libvirt, or proxmox libvirt
EVETEST_API_PORT gRPC API port exposed by evetest container 50021
EVETEST_API_ADDRESS IP/hostname of the machine running the evetest container, used by the CLI to connect remotely (unset = localhost) --

When running multiple evetests in parallel on the same host, each test must use a different EVETEST_API_PORT to avoid port conflicts. The evetest CLI finds local instances on its own (see Running Several Instances). Only when running the CLI against an instance on a remote machine, set EVETEST_API_PORT and/or EVETEST_API_ADDRESS in the same terminal session beforehand.

Version Overrides
Variable Description Default
EVETEST_ORG Docker Hub organization for evetest and evetest-broker images lfedge
EVETEST_EVE_REPO EVE image repository lfedge/eve
EVETEST_ADAM_VERSION Adam controller version (build-time only, see note below) 0.0.87
EVETEST_SDN_VERSION SDN emulator image tag content hash of sdn/ (make sdn-version)

EVETEST_ADAM_VERSION requires evetest container rebuild. Unlike the other variables above, Adam's binary is baked into the evetest image at build time (Dockerfile.evetest copies it in from lfedge/adam:$EVETEST_ADAM_VERSION); the evetest image tag is a hash of the evetest sources, so it does not encode which Adam version is inside. Setting EVETEST_ADAM_VERSION as a runtime environment variable before make evetest has no effect on which Adam binary actually runs. To test against a different (e.g. custom-built) Adam version, rebuild the image first: make build-container EVETEST_ADAM_VERSION=<version>, then run tests as usual against that locally-built image.

If the Adam image was built locally (e.g. docker build -t lfedge/adam:<version> . in your own adam checkout) rather than pulled from a registry, the build may fail with failed to resolve source metadata for docker.io/lfedge/adam:<version>: not found even though docker images shows it present. This happens when your active docker buildx builder uses the docker-container driver, which runs in its own isolated BuildKit instance with no visibility into the host's local image store (docker buildx ls shows which driver is active). Work around it by pointing this one build at the default builder (which shares the host's Docker image store), without changing your global default: BUILDX_BUILDER=default make build-container EVETEST_ADAM_VERSION=<version>.

Broker Variables (for distributed mode)

These are read by evetest-broker itself, not the evetest container -- set them in the broker's own environment (make libvirt-run-broker-container, or the Proxmox broker VM's /etc/evetest/broker.env, written by the Proxmox broker installer). Which ones apply depends on EVETEST_BROKER_DEVICE_PROVIDER.

Common to every provider:

Variable Description Default
EVETEST_BROKER_IMAGE_DIR VM image storage directory $HOME/.evetest (all-in-one / qemu provider), /home/eve-broker/images (libvirt provider; created and configured by make libvirt-setup-broker-user), /root/.evetest/images (proxmox provider; set by the Proxmox broker installer)
EVETEST_SDN_UPLINK_IPV4_SUBNET IPv4 subnet for SDN uplink 192.168.170.0/24
EVETEST_SDN_UPLINK_IPV6_SUBNET IPv6 subnet for SDN uplink fd11:778b:03dd:2222::/64
EVETEST_BROKER_PROXY_CA_CHAIN Proxy CA certificate chain file --
EVETEST_BROKER_MAX_CLIENTS Max concurrent evetest clients the broker will accept; new connections are rejected with an error once this many are already connected (reconnects of existing clients are never blocked) -1 (unlimited)
EVETEST_BROKER_DOCKER_IMAGE_RETENTION How long, in minutes, an unused, evetest-managed Docker image (one the broker itself pulled or built) is kept before the broker's periodic cleanup removes it 10080 (7 days)
EVETEST_BROKER_DOCKER_DISK_USAGE_THRESHOLD Disk usage percent (on the filesystem backing Docker's storage) at or above which the broker aggressively evicts the oldest unused, evetest-managed Docker images, regardless of the retention setting above -- but only while free space is also below EVETEST_BROKER_DOCKER_MIN_FREE_GIB 80
EVETEST_BROKER_DOCKER_MIN_FREE_GIB Free-space floor, in GiB, on the filesystem backing Docker's storage. Aggressive Docker image eviction needs both usage at or above EVETEST_BROKER_DOCKER_DISK_USAGE_THRESHOLD and free space below this floor, so a big disk that is merely over the percentage is left alone. Keep it larger than EVETEST_BROKER_TEMPLATE_MIN_FREE_GIB so Docker images are evicted before templates. 0 disables the floor (percentage-only eviction) 150
EVETEST_BROKER_TEMPLATE_RETENTION How long, in minutes, an unused EVE disk-image template (see The EVE Image Template Cache) is kept before the broker's periodic cleanup removes it. Deliberately generous, since templates let consecutive runs against the same EVE version skip the image build entirely; zero or negative disables age-based eviction, but disk-usage-based eviction still applies regardless. A template still backing a live VM is never removed regardless of this value 10080 (7 days)
EVETEST_BROKER_TEMPLATE_DISK_USAGE_THRESHOLD Disk usage percent (on the filesystem backing the broker's image directory) at or above which the broker evicts the oldest unreferenced EVE image templates, regardless of the retention setting above -- but only while free space is also below EVETEST_BROKER_TEMPLATE_MIN_FREE_GIB. Deliberately higher than EVETEST_BROKER_DOCKER_DISK_USAGE_THRESHOLD: broker hosts routinely idle above 80%, so an 80% threshold would evict every unreferenced template on every pass and the cache would never stay warm; templates are also the wrong thing to give up first -- one is 1-2 GB, where the Docker image store is tens of GB 90
EVETEST_BROKER_TEMPLATE_MIN_FREE_GIB Free-space floor, in GiB, on the filesystem backing the broker's image directory. Template eviction under disk pressure needs both usage at or above EVETEST_BROKER_TEMPLATE_DISK_USAGE_THRESHOLD and free space below this floor: on a 3 TB disk at 90% about 300 GB is still free, which is no reason to evict every template. 0 disables the floor (percentage-only eviction) 100
EVETEST_BROKER_PPROF_PORT Port for the broker's net/http/pprof debug endpoint (listens on all interfaces); 0 disables it 0 (disabled)

Disk-pressure eviction floors. Existing deployments that set only the percentage variables now also get the 100 GiB (templates) and 150 GiB (Docker) free-space floors, so on large disks eviction happens less often than before; set EVETEST_BROKER_TEMPLATE_MIN_FREE_GIB=0 / EVETEST_BROKER_DOCKER_MIN_FREE_GIB=0 to restore the old percentage-only behaviour. The Proxmox broker installer template exposes only the Docker percentage, so those hosts get the default floors. The floor is compared against the space available to unprivileged users (statfs f_bavail).

libvirt provider only:

Variable Description Default
EVETEST_BROKER_LIBVIRT_URI Libvirt connection URI (not yet configurable -- the provider always uses this value) qemu:///system

proxmox provider only (see deploy/proxmox/README.md for the full setup guide; in normal use these are generated for you by the Proxmox broker installer, not set by hand):

Variable Description Default
EVETEST_BROKER_PROXMOX_API_URL Proxmox VE REST API base URL, e.g. https://192.168.1.50:8006/api2/json -- (required)
EVETEST_BROKER_PROXMOX_PASSWORD Password for the root@pam Proxmox user. Must be the literal root@pam account, not an API token -- Proxmox hardcodes some VM options (hookscript, args) as settable only by a real root@pam session regardless of a token's ACLs -- (required)
EVETEST_BROKER_PROXMOX_NODE Proxmox node name to create VMs on auto-detected on a single-node install; required for multi-node clusters
EVETEST_BROKER_PROXMOX_STORAGE Proxmox storage ID used for VM disks, e.g. local-lvm -- (required)
EVETEST_BROKER_PROXMOX_IMPORT_STORAGE Proxmox storage ID (with the import content type enabled) that VM disk images are uploaded to before being imported into VM disk storage local
EVETEST_BROKER_PROXMOX_TLS_SKIP_VERIFY Disable TLS certificate verification for the Proxmox API connection (useful with the default self-signed PVE certificate) false

Deployment Modes

All-in-One Mode

In all-in-one mode, everything runs within a single Docker container: the test runner, Adam controller, an embedded broker, and all VMs (EVE + SDN) are created using QEMU inside the container.

This is the default mode when EVETEST_BROKER_ADDRESS is not set.

All-in-One evetest setup

make evetest NAME=TestDHCPIPv4Only

The container is started with NET_ADMIN capability and access to the Docker socket. /dev/kvm is passed through when available (on Linux with KVM support; not available on macOS, where Docker Desktop's Linux VM does not support nested virtualization). Without KVM, QEMU falls back to TCG software emulation. The embedded broker uses QEMU directly to start and manage EVE and SDN VMs. The checkout's eve-tools/bpftrace-compiler is mounted read-only at /eve-tools/bpftrace-compiler, with its linuxkit cache and its cache of compiled scripts persisted under ~/.linuxkit/cache and ~/.evetest/bpftrace-compiler-cache, for tests that compile a bpftrace script for the device's kernel (see TestVGAPassthroughNoHostAccess).

Best for:

  • Local development on laptops
  • Learning the framework and experimenting with tests
  • Quick iteration during test development
  • Scenarios where you don't have access to a remote hypervisor

Requires nested virtualization support.

Distributed Mode

In distributed mode, the evetest container runs on one machine (a CI runner or your laptop) while the broker runs on a separate, (typically more powerful) hypervisor server. EVE VMs run directly on the host hypervisor, avoiding nested virtualization (which would occur in All-in-One Mode when executed inside a virtualized CI runner).

The broker uses a device provider (libvirt or proxmox; see Broker (evetest-broker) below) to create VMs and acts as a tunnel proxy, forwarding IP packets between the evetest container and the SDN VM. From the test's perspective, this is transparent -- the same test code works in both modes.

Distributed evetest setup

With the libvirt provider, the broker runs as a container on the hypervisor host itself:

# One-time setup on the hypervisor server:
sudo make libvirt-setup-broker-user
make libvirt-run-broker-container

# On the runner/laptop (192.168.1.100 is example of the server IP address):
EVETEST_BROKER_ADDRESS=192.168.1.100 make evetest NAME=TestDHCPIPv4Only

With the proxmox provider, the broker instead runs inside a VM on the Proxmox host, talking to the Proxmox API to create/manage EVE and SDN VMs -- see deploy/proxmox/README.md for the installer that sets this up end to end (no manual broker setup step; it prints the EVETEST_BROKER_ADDRESS to use once ready).

Best for:

  • CI/CD pipelines (small runners, powerful hypervisors)
  • Multiple developers/CI jobs sharing the same hypervisor infrastructure
  • Resource-intensive tests (multi-device, cluster testing)

Multiple evetest instances can connect to the same broker concurrently. The broker tracks resources per client and ensures isolation between concurrent tests.

Example: Running Evetest on a CI Server

Continuous integration server example

Architecture

EVETest consists of several components that communicate over gRPC:

EVETest Container

The container is the execution environment where tests run. Inside it you'll find:

  • The Go test binary executing your test code
  • The Adam controller -- EVE's open-source controller implementation
  • The evetest CLI for interactive debugging (if you prefer not to install it directly on your host)
  • Optionally, an embedded broker (in all-in-one mode)

The container has the evetest framework and its dependencies baked in. Only evetest/tests/ and evetest/netmodels are mounted at runtime (allowing live test edits without rebuilding the image). Optionally, /artifacts is mounted for collecting outputs. It runs with NET_ADMIN capability for network configuration and tunnel management.

Running tests inside a container ensures a consistent, isolated, and reproducible environment. All dependencies (including QEMU) are encapsulated within the container, eliminating version mismatch problems and preventing interference with the host system.

Broker (evetest-broker)

The broker is the resource management and hypervisor abstraction layer. It translates test requirements ("I need an EVE device with 4GB RAM and 2 network interfaces") into actual VMs on a hypervisor.

Device management is implemented through a provider interface, allowing different hypervisor backends to be plugged in and enabling future implementations for additional platforms.

The broker currently supports the following device providers:

  • QEMU — direct QEMU invocation, used in all-in-one mode
  • libvirt — uses libvirt APIs, used in distributed mode; the broker runs as a container on the hypervisor host (see make libvirt-setup-broker-user / make libvirt-run-broker-container above). Requires CGO, which is only enabled for a native broker build (build host arch == target arch) -- building make build-broker-container directly on an amd64 or arm64 host gets full libvirt support either way. The published arm64 broker image is cross-compiled from an amd64 CI runner, so it is built with CGO_ENABLED=0 and falls back to a stub that returns an error if the libvirt provider is selected (see the note on make build-broker-container below)
  • proxmox — uses the Proxmox VE REST API, used in distributed mode; the broker itself runs inside a VM on the Proxmox host, set up end to end by the installer in deploy/proxmox/README.md

It manages the VM lifecycle (create, power on/off, reboot, destroy), caches EVE images for reuse across tests, and acts as a tunnel proxy forwarding IP packets between the evetest container and the SDN VM. This tunneling allows the evetest container to operate without direct network connectivity to the VMs -- it only needs access to the broker.

The EVE Image Template Cache

Building an EVE image used to mean one full container build per device -- roughly 4 minutes and 2 GB of I/O -- run serially, even though most of that work (unpacking the container, laying out the disk) does not depend on anything device-specific like the onboarding certificate. The broker now builds a configuration-independent template once per distinct (docker image content ID, disk size, installer flag, arch) and reuses it across every device and every test run that matches:

  • Templates are cached under $EVETEST_BROKER_IMAGE_DIR/templates/<key>/, keyed by content rather than by EVE version string, so identical image content built under a different tag still hits the cache.
  • Each device gets its own qcow2 copy-on-write overlay backed by the template's disk, with that device's own 5 MiB FAT config partition written into the overlay's CONFIG partition -- the template itself is never modified.
  • Templates are reference-counted: one currently backing a live VM is never deleted, regardless of age or disk pressure.
  • An image-directory-wide flock guards template creation and eviction, so two brokers sharing the same EVETEST_BROKER_IMAGE_DIR cannot destroy each other's state.
  • Unreferenced templates are evicted by age (EVETEST_BROKER_TEMPLATE_RETENTION) and by disk pressure (EVETEST_BROKER_TEMPLATE_DISK_USAGE_THRESHOLD together with the EVETEST_BROKER_TEMPLATE_MIN_FREE_GIB floor), mirroring the existing Docker image cleanup.

Every provider uses the cache; they differ only in how a device's disk is derived from a template. libvirt and qemu use overlays, since both attach local image files directly. proxmox uses a standalone copy: it uploads each device's disk to the PVE node, where a backing file would not exist. A live-image template is deliberately keyed without the disk size, so the per-device disk -- overlay or copy -- is grown to the requested size instead.

With the proxmox provider that copy is what the broker uploads, so the per-device cost there is one full-size copy plus the upload to the node, against a ~4-minute container build per device before.

With the qemu provider the broker lives inside the short-lived evetest container, so its 30-minute cleanup loop would rarely tick before the container exits. There the sweep runs once at broker startup instead -- same age and disk-pressure rules, same variables.

SDN (Software Defined Network)

The SDN is a lightweight LinuxKit-based VM that models physical network infrastructure in software. It provides the network environment that EVE devices connect to.

Tests define a declarative network model specifying the topology: ports, bridges, bonds, VLANs, DHCP servers, DNS servers, firewalls, HTTP proxies, SCEP servers, and 802.1X authentication. The SDN applies this model using standard Linux networking tools (bridges, namespaces, iptables, dnsmasq, hostapd, etc.) and provides a realistic environment for network testing.

The SDN is implemented as a VM (rather than a container) because it needs multiple network namespaces for complex topologies and L2 connectivity with EVE VMs.

For a detailed description of the SDN architecture, network model reference, predefined models, CLI commands, and build instructions, see sdn/README.md.

EVETest CLI

A Cobra-based command-line tool for interacting with running test instances. It communicates with the evetest container via gRPC and provides commands for inspecting device state, viewing logs, SSH access, console access, and test flow control.

The CLI can run inside the container or on the host. Remote access commands (ssh, scp, console) work transparently regardless of deployment mode -- the framework handles tunnel and proxy setup automatically.

Adam Controller

Adam is EVE's open-source controller implementation. It runs inside the evetest container and handles device onboarding, configuration distribution, and collection of device info, metrics, and logs. Tests interact with EVE devices through Adam, and the framework subscribes to Adam's data streams to keep device state up to date.

Makefile Targets

Target Description
make list-tests List all available tests and test suites with their configurable parameters
make evetest Run a test (requires NAME=...)
make version / make sdn-version Print the image tag (content hash of the sources) of the evetest and broker images / of the SDN image; see Image Tags
make build-container Build the evetest container; set DOCKER_PLATFORM=linux/<arch> to cross-compile for a different architecture (Go build stage runs natively on the host, no QEMU emulation); use DOCKER_TARGET=push to publish to a registry instead of loading locally
make build-broker-container Build broker Docker image only; set DOCKER_PLATFORM=linux/<arch> to cross-compile for a different architecture (Go build stage runs natively on the host, no QEMU emulation); CGO (and therefore the libvirt provider) is only enabled when DOCKER_PLATFORM matches the build host's own arch -- e.g. building on an arm64 host with the default DOCKER_PLATFORM gets full libvirt support, but cross-compiling to a different arch than the host (as CI does to publish the arm64 image from an amd64 runner) sets CGO_ENABLED=0 and drops libvirt (qemu and proxmox providers are unaffected either way); use DOCKER_TARGET=push to publish to a registry instead of loading locally
make build-sdn-container Build SDN VM container (requires linuxkit, with the LINUXKIT variable pointing to the binary); set DOCKER_PLATFORM=linux/<arch> to cross-compile for a different architecture (Go build stage runs natively on the host, no QEMU emulation); use DOCKER_TARGET=push to publish to a registry instead of loading locally
make build-test-apps Build all test apps under testapps/ by invoking each app's build target; supports the same DOCKER_PLATFORM, DOCKER_TARGET, and EVETEST_ORG variables
make install-cli Install the evetest CLI binary
make proto Regenerate protobuf Go code from .proto files (Docker-based)
make libvirt-setup-broker-user One-time setup for the libvirt broker (requires sudo)
make libvirt-run-broker-container Start the broker container for the libvirt provider (distributed mode)
make proxmox-broker-installer Assemble the self-contained Proxmox broker installer script (see deploy/proxmox/README.md)

Advanced Host Setup

Pull-Through Cache (Registry Mirror)

By default, EVE pulls container images directly from the upstream registries (docker.io, ghcr.io, quay.io, etc.). This can be slow or fail due to rate limits, especially in CI environments or when running many tests. The EVETEST_REGISTRY_MIRROR_* variables route image pulls for specific registries through a local pull-through cache.

Each variable accepts a full mirror URL including scheme and, if needed, a path (e.g. a Harbor proxy-cache project): [scheme://]host:port[/path].

proxmox provider: none of the manual setup below is needed. Pass --with-oci-registry-mirrors to the Proxmox broker installer and it runs its own pull-through cache containers on the broker VM and sends their addresses to every evetest client automatically -- no EVETEST_REGISTRY_MIRROR_* variables to set by hand. Set one of them yourself only to override a specific registry's mirror.

The mirror is applied in two places automatically:

  • Application docker-datastore config: EVE is configured to pull app images from the per-registry mirror URL instead of the upstream registry. This applies to any registry that has a mirror configured.
  • Containerd mirror config: for kubevirt devices, per-registry hosts.toml files are written under /etc/containerd/certs.d/ inside the kube container before containerd starts, and config_path is added to /etc/containerd/config-k3s.toml. EVE's K3s setup uses an externally-managed containerd (not K3s-managed), so K3s's own registries.yaml is ineffective; configuring containerd directly is the correct approach. Only registries that have a mirror configured get a hosts.toml entry. This is a temporary solution until EVE is enhanced to allow configuring a registry mirror for K3s/containerd through device config.
Deploying Docker Registry as a Docker Hub Mirror

The official registry:2 image can act as a pull-through cache for Docker Hub. It is lightweight and requires no additional setup beyond a single docker run.

docker run -d --restart=always --name docker-hub-mirror \
  -p 5000:5000 \
  -e REGISTRY_PROXY_REMOTEURL=https://registry-1.docker.io \
  -e REGISTRY_PROXY_USERNAME=<your-dockerhub-username> \
  -e REGISTRY_PROXY_PASSWORD=<your-dockerhub-token> \
  registry:2

Replace <your-dockerhub-username> and <your-dockerhub-token> with a Docker Hub personal access token (create one at hub.docker.com → Account Settings → Personal Access Tokens). Credentials are required to avoid Docker Hub rate limits.

Run evetest with only the docker.io mirror set:

HOST_IP=$(ip route get 8.8.8.8 | awk '{for(i=1;i<=NF;i++) if($i=="src") print $(i+1); exit}')
EVETEST_REGISTRY_MIRROR_DOCKER=http://$HOST_IP:5000 make evetest NAME=MyTest

To check what images have been cached so far:

curl http://$HOST_IP:5000/v2/_catalog

Note: registry:2 only mirrors one upstream registry per instance. For other registries (ghcr.io, quay.io, etc.) use Harbor (see below) or separate instances on different ports.

Deploying Harbor as a Local Pull-Through Cache

Harbor is an OCI-compliant registry that supports pull-through proxy caching. The steps below deploy it locally using its Docker Compose installer.

  1. Download and extract the installer

    wget https://github.com/goharbor/harbor/releases/latest/download/harbor-online-installer-v2.15.0.tgz
    tar xzvf harbor-online-installer-v2.15.0.tgz
    cd harbor
    

    Replace v2.15.0 with the latest release from the Harbor releases page.

  2. Find your host IP

    Use the host's LAN IP so both the evetest container and EVE VMs can reach Harbor:

    # IP used for outbound traffic (reliable on most setups)
    HOST_IP=$(ip route get 8.8.8.8 | awk '{for(i=1;i<=NF;i++) if($i=="src") print $(i+1); exit}')
    echo $HOST_IP
    
  3. Configure Harbor

    cp harbor.yml.tmpl harbor.yml
    

    Edit harbor.yml:

    • Set hostname to $HOST_IP
    • Comment out the https: block entirely (for a simple HTTP-only internal deployment)
    • Set a strong harbor_admin_password
    hostname: 192.168.1.100        # replace with your $HOST_IP
    
    # https:                       # comment out for HTTP-only
    #   port: 443
    #   certificate: ...
    #   private_key: ...
    
    harbor_admin_password: MySecretPassword
    
  4. Install and start Harbor

    sudo ./install.sh
    

    Harbor is now accessible at http://$HOST_IP.

  5. Configure proxy cache projects in Harbor

    For each upstream registry you want to mirror, create a proxy cache project in Harbor's UI (http://$HOST_IP → Projects → New Project → check "Proxy Cache"):

    Project name Registry endpoint
    docker-io-proxy https://hub.docker.com
    ghcr-io-proxy https://ghcr.io
    quay-io-proxy https://quay.io
    registry-k8s-io-proxy https://registry.k8s.io
    gcr-io-proxy https://gcr.io
    mcr-proxy https://mcr.microsoft.com

    For each one:

    • Go to Administration → Registries → New Endpoint, enter the endpoint URL, and save.
    • Go to Projects → New Project, check Proxy Cache, select the endpoint.
    • Set the project Access Level to Public so containerd can pull without authentication.
  6. Run evetest with the mirror

    EVETEST_REGISTRY_MIRROR_DOCKER=http://$HOST_IP:80/docker-io-proxy \
    EVETEST_REGISTRY_MIRROR_GHCR=http://$HOST_IP:80/ghcr-io-proxy \
    EVETEST_REGISTRY_MIRROR_QUAY=http://$HOST_IP:80/quay-io-proxy \
    EVETEST_REGISTRY_MIRROR_K8S=http://$HOST_IP:80/registry-k8s-io-proxy \
    EVETEST_REGISTRY_MIRROR_GCR=http://$HOST_IP:80/gcr-io-proxy \
    EVETEST_REGISTRY_MIRROR_MCR=http://$HOST_IP:80/mcr-proxy \
    make evetest NAME=MyTest
    

    Each value includes the http:// scheme and the Harbor project path. Only the registries you need to mirror must be set; the others pull directly from their origin.

Note: For app image pulls (docker-datastore config), the scheme is stripped before being passed to EVE — EVE's docker:// FQDN prefix is a registry-type marker, not a transport scheme. If the mirror requires authentication, add its credentials to your Docker credential store on the host so the evetest container can read and forward them to EVE.

NetworkManager Interference (libvirt provider)

In the distributed deployment mode, you must prevent NetworkManager (if present) from managing the xconnect bridges created by libvirt provider. This configuration must be applied on the host running the broker; it is not required on the test-runner host system or in the all-in-one deployment mode.

sudo vim /etc/NetworkManager/conf.d/99-evetest-unmanaged.conf
[device-evetest-xconnect-unmanaged]
match-device=interface-name:evetest-x-*
managed=0

These bridges are created to interconnect EVE VMs with the SDN VM and must not be managed by the host.

sudo systemctl restart NetworkManager
Docker Bridge MTU vs. a Lower-MTU Network Path

In distributed deployment mode, traffic between the evetest container and the broker travels over whatever network path connects the two hosts. If that path has a lower MTU than Docker's default bridge MTU (1500) -- e.g. a VPN tunnel between the test-runner host and the broker's network -- large gRPC responses can silently stall: TCP segments sized for the full 1500-byte MTU get dropped on the lower-MTU link, and if the path also blocks the ICMP "Fragmentation Needed" messages that would normally let TCP discover and adapt to the real path MTU (common behind corporate firewalls), the same oversized segment gets retransmitted and dropped repeatedly until the connection is eventually reset. Symptom: a test fails partway through a long-running RPC (e.g. BuildImage) with rpc error: code = Unavailable desc = error reading from server: ... connection reset by peer, even though small messages (including gRPC keepalive pings) keep flowing fine right up until the failure.

Check for a mismatch:

ip link show <vpn-interface>   # e.g. ppp0, tun0, wg0
ip link show docker0

If the VPN/tunnel interface's MTU is lower than docker0's (1500), lower Docker's bridge MTU to match (or a little under) it in /etc/docker/daemon.json:

{
    "mtu": 1400
}

Then restart Docker (sudo systemctl restart docker) and recreate any evetest containers/networks -- this only affects the default bridge and networks created afterward, not ones already running.

Enabling IPv6 Connectivity Tests

In all-in-one deployment mode, IPv6 connectivity between EVE devices and the controller works out of the box and does not require any special host setup, even if the host system itself does not have IPv6 connectivity.

However, tests that require IPv6 Internet access (RequireInternetConnectivity{RequireIPv6: true}) will be skipped if the host does not have IPv6 connectivity.

proxmox provider: the steps below don't apply -- IPv6 for the SDN uplink is provided by Proxmox's own SDN zone (DHCPv6 + SNAT), not by Docker/host NAT66. The Proxmox broker installer sets this up automatically as part of creating the evu uplink VNet; see that doc's Prerequisites section for the (different) host-level IPv6 requirements specific to Proxmox SDN.

For tests requiring IPv6 Internet access, you must also:

  1. Enable IPv6 in Docker -- all-in-one mode only, skip for distributed modes:

    # Add to the docker daemon config (generate subnet using https://unique-local-ipv6.com/):
    cat /etc/docker/daemon.json
    {
        "ipv6": true,
        "fixed-cidr-v6": "fdbd:e2c4:bec9:8249::/64"
    }
    # Then restart docker daemon:
    sudo systemctl restart docker
    
  2. Enable IPv6 forwarding and NAT66 on the host -- needed for all-in-one mode and for distributed mode with the libvirt provider (there, apply this on the host running libvirt, not the host running the evetest container):

    sudo sysctl -w net.ipv6.conf.all.forwarding=1
    sudo modprobe ip6table_nat
    sudo ip6tables -t nat -A POSTROUTING -o <egress-interface> -j MASQUERADE
    

    Replace <egress-interface> with the host interface used for external connectivity.

Contributing

Directory Structure
evetest/
├── broker/             # Broker binary (VM lifecycle, tunnel proxy)
│   └── provider/       # Device provider implementations (QEMU, libvirt, Proxmox)
├── cli/                # evetest CLI binary
├── constants/          # Shared constants and Viper config
├── controller/         # Adam controller client (Go wrapper around Adam REST API)
├── grpcapi/
│   ├── proto/          # Protobuf service definitions (broker.proto, sdn.proto, ...)
│   ├── go/             # Generated Go code (do not edit manually)
│   └── eve-api/        # git submodule: lf-edge/eve-api (pinned; proto/ used for imports)
├── logger/             # Logging utilities
├── protobuilder/       # Helpers for building protobuf config messages
├── sdn/
│   └── vm/             # SDN agent (separate Go module, built into LinuxKit VM)
│       └── pkg/configitems/  # Network config item implementations
├── tests/              # Integration tests (mounted into container at runtime)
│   ├── networking/     # Network-related tests and test suites
│   ├── cluster/        # Kubernetes cluster tests
│   └── lps/            # Local Profile Server tests
├── netmodels/          # Reusable network model definitions for tests (mounted into container at runtime)
├── utils/              # Shared utilities (crypto, networking, etc.)
├── content-hash.sh     # Computes the image tags (content hashes of the sources)
├── Makefile
├── Dockerfile.evetest  # Evetest container image
└── Dockerfile.broker   # Broker container image
Go Modules

The project consists of two Go modules:

Module Path Description
github.com/lf-edge/eve/evetest evetest/ Main framework (harness, EdgeDevice API, gRPC server, broker, CLI) and integration tests
github.com/lf-edge/eve/evetest/sdn/vm evetest/sdn/vm/ SDN agent built into the LinuxKit VM

Dependency graph:

evetest  ◀──depends on──  sdn/vm
(tests are packaged within the evetest module)

When you change dependencies, run go mod tidy in the affected module's directory.

Image Tags

There are no version files to bump. The Docker image tags are content hashes of the sources baked into each image, computed by evetest/content-hash.sh from the working tree (committed, modified and untracked files, but not git-ignored ones; Markdown files are ignored). A given tag therefore always contains exactly the sources it was hashed from, and a published image is never overwritten with different content.

Command Tag of Hashed sources
make version lfedge/evetest and lfedge/evetest-broker evetest/ except tests/, netmodels/, matchers/, testapps/, sdn/, deploy/ and the eve-api submodule
make sdn-version lfedge/evetest-sdn evetest/sdn/ except pics/

make evetest expects the images with these tags: for each it uses the local one, else tries to pull it, else builds it. The SDN image is not checked when EVETEST_SDN_VERSION is set. With a remote broker (EVETEST_BROKER_ADDRESS) the local SDN image is still ensured: the broker pulls the image itself, and only if that fails (e.g. the image was built from an unmerged change) does the harness push the local copy to it. CI (publish-evetest.yml) publishes the images of master under the same tags.

Changes to tests (evetest/tests/) do not change the tag, because tests are compiled inside the container at runtime from the mounted source. A go.mod change does, which keeps the module cache baked into the image current.

Every edit of the framework or SDN sources yields a new tag and thus a new local image; remove the stale ones with docker image prune -a or by tag.

Manual Steps After Changes

Images are rebuilt automatically by make evetest (see Image Tags). Only these steps are not automatic:

  • gRPC API (grpcapi/proto/): regenerate the Go code with make proto.
  • eve-api submodule (grpcapi/eve-api/): check out the desired commit (git -C grpcapi/eve-api checkout <commit>), git add grpcapi/eve-api from within evetest/, then run make proto.
  • CLI (cli/): make install-cli to update the evetest binary on the host (the copy inside the container is rebuilt automatically).
  • Shared packages used by sdn/vm (constants/, utils/, grpcapi/): sdn/vm imports them as a pinned Go module version, so bump that version in sdn/vm/go.mod (and run go mod tidy there) for the SDN image to pick up the change.

Documentation

Index

Constants

View Source
const (
	// FaultAnswerStatus answers the request with ControllerFault.StatusCode
	// instead of passing it to the controller.
	FaultAnswerStatus = controller.FaultActionStatus
	// FaultCloseConn drops the connection without answering, which the device
	// sees as a failure to reach the controller rather than as an answer.
	FaultCloseConn = controller.FaultActionCloseConn
	// FaultDelay passes the request on after ControllerFault.Delay, to exercise
	// slow answers and timeouts.
	FaultDelay = controller.FaultActionDelay
)

Ways in which the controller can be made to fail a device request.

View Source
const (

	// KiB is the number of bytes in a kibibyte.
	KiB uint64 = 1 << (10 * iota)
	// MiB is the number of bytes in a mebibyte.
	MiB
	// GiB is the number of bytes in a gibibyte.
	GiB
	// TiB is the number of bytes in a tebibyte.
	TiB
)
View Source
const (
	// DefaultSFTPUsername is the username accepted by evetest's built-in
	// SFTP image server (see GetImageServerIPv4 / GetImageServerSFTPPort).
	// Use it (together with DefaultSFTPPassword) when configuring SFTPStorage.
	DefaultSFTPUsername = "evetest"
	// DefaultSFTPPassword is the password accepted by evetest's built-in
	// SFTP image server.
	DefaultSFTPPassword = "pass123"
)
View Source
const (
	USBHubVendorID  = 0x0409
	USBHubProductID = 0x55aa
)

USBHubVendorID and USBHubProductID are the ids of QEMU's emulated USB hub (hw/usb/hub.c), the one AttachUSBHub plugs in; a test tells the hubs from the drives by them.

View Source
const CPUsParameterKey = "CPUS"

CPUsParameterKey is the key used for the CPUs parameter.

View Source
const DiskSizeMiBParameterKey = "DISK_SIZE_MB"

DiskSizeMiBParameterKey is the key used for the DiskSizeMiB parameter.

View Source
const EVEKubeAppNamespace = "eve-kube-app"

EVEKubeAppNamespace is the Kubernetes namespace EVE runs app workloads in; both VMIRS objects and their PVCs live there. See pkg/pillar/kubeapi.EVEKubeNameSpace.

View Source
const EVEVersionParameterKey = "EVE_VERSION"

EVEVersionParameterKey is the key used for the EVE version parameter. This is the EVETEST_EVE_VERSION environment variable, which defaults to the HEAD of the checked-out EVE repo when not set (determined by the evetest Makefile). Most tests do not need to explicitly use this parameter and will automatically get EVE device(s) running the version defined by the EVETEST_EVE_VERSION variable. Only needed when a test must start from a different EVE version and then later upgrade to the version selected by EVETEST_EVE_VERSION, or when a test suite needs to override the EVE version for a specific sub-test variant.

View Source
const FilesystemParameterKey = "FILESYSTEM"

FilesystemParameterKey is the key used for the Filesystem parameter.

View Source
const HypervisorParameterKey = "HYPERVISOR"

HypervisorParameterKey is the key used for the Hypervisor parameter.

View Source
const RAMSizeMiBParameterKey = "RAM_SIZE_MB"

RAMSizeMiBParameterKey is the key used for the RAMSizeMiB parameter.

View Source
const TPMParameterKey = "TPM"

TPMParameterKey is the key used for the TPM parameter.

View Source
const USBControllerBus = "evxhci.0"

USBControllerBus is the QEMU bus name of the USB 3.0 (xHCI) controller that every EVE device VM has on the qemu and proxmox providers, for device_add.

Variables

View Source
var NilUUID = uuid.UUID{}

NilUUID is special form of UUID that is specified to have all 128 bits set to zero.

Functions

func AddImageServerFile

func AddImageServerFile(name string, content []byte) string

AddImageServerFile writes content to a file served by evetest's built-in image server (both over HTTP and SFTP), returning its filename for use as HTTPStorage.ImageRelativePath / SFTPStorage.ImageRelativePath.

func ArmControllerFault

func ArmControllerFault(fault ControllerFault)

ArmControllerFault makes the controller fail the device requests matching the given description, until the fault is used up (ControllerFault.Count) or cleared with ClearControllerFaults. Several faults can be armed at once; the first one matching a request applies.

Requires the proxy - see WithControllerFaults. Only the requests devices make can be failed: the harness reaches the controller directly, so watching info or metrics is never disturbed by an injected fault.

For example, to make every info message fail with 503 while leaving the device config alone:

evetest.ArmControllerFault(evetest.ControllerFault{
	Method:     "POST",
	PathSuffix: "/info",
	Action:     evetest.FaultAnswerStatus,
	StatusCode: http.StatusServiceUnavailable,
})

func Checkpoint

func Checkpoint(name string)

Checkpoint marks a significant execution point in a test.

Each checkpoint is identified by a name. If the environment variable EVETEST_PAUSE_ON_CHECKPOINT is set to the same name, test execution will pause when this checkpoint is reached.

The test can be resumed via the CLI command:

evetest continue [--until <next-checkpoint>]

This mechanism is primarily intended for interactive debugging and step-by-step inspection of long-running or complex tests.

func ClearControllerFaults

func ClearControllerFaults()

ClearControllerFaults removes every armed fault, so that the controller answers device requests normally again.

func Close

func Close()

Close gracefully shuts down the test harness and releases all resources created during Init and Setup.

If the test is running as part of a suite, Close performs no teardown and returns immediately, allowing shared resources (such as VMs and network infrastructure) to be reused by subsequent test cases.

Otherwise, Close stops all background goroutines, shuts down the internal gRPC server, disconnects from the broker (triggering cleanup of all associated EVE and SDN devices), removes any SDN tunnel interfaces created by the test, and stops the Adam controller.

func ControllerFaultsEnabled

func ControllerFaultsEnabled() bool

ControllerFaultsEnabled reports whether the controller runs behind the fault injecting proxy. A test which arms faults has to skip when it returns false, which happens when the user turned the proxy off with EVETEST_CONTROLLER_FAULTS=false.

func CreateBlankImageFile

func CreateBlankImageFile(
	name string, format eveconfig.Format, sizeBytes uint64) (relativePath, sha256Hex string)

CreateBlankImageFile creates an empty disk image of the given format and size, served by evetest's built-in image server (both over HTTP and SFTP), and returns its filename (for ImageRelativePath) together with the hex-encoded SHA256 of the resulting file's content (for ImageSHA256).

Requires qemu-img to be available inside the evetest container (already a build dependency of the broker).

func CreateRandomImageFile

func CreateRandomImageFile(name string, sizeBytes uint64) (relativePath, sha256Hex string)

CreateRandomImageFile creates a file of sizeBytes random bytes, served by evetest's built-in image server (see AddImageServerFile), and returns its filename (for ImageRelativePath) together with the hex-encoded SHA256 of its content (for ImageSHA256).

Random (non-blank) content makes ImageSHA256 verification meaningful: a blank file's checksum can't distinguish "downloaded correctly" from "downloaded as all zeros/corrupted-but-still-blank" -- a corrupted download of random content is vanishingly unlikely to still match the checksum computed here.

func DefineTestParameters

func DefineTestParameters(params ...TestParameterDefinition)

DefineTestParameters defines the set of parameters available to the currently executing test or test suite.

func DiskName

func DiskName(extraDiskIdx uint) string

DiskName returns the virtio device name (e.g. "/dev/vdc") of the zero-based extraDiskIdx-th extra (non-boot) disk requested via RequireEdgeDevice.ExtraDisks: extraDiskIdx 0 is /dev/vdb, since /dev/vda is always the device's boot disk.

func FaultForDuration

func FaultForDuration(fault ControllerFault, window time.Duration, run func())

FaultForDuration is a convenience for a fault which stays armed for a window of time rather than for a number of requests.

func FetchAndServeImageFile

func FetchAndServeImageFile(url, name, sha256Hex string) string

FetchAndServeImageFile downloads url over the evetest container's own network and re-serves it from evetest's built-in image server, so a device with no outbound Internet reachability can still download it as a normal HTTP datastore -- just a local one instead of the remote url. Retries a few times on transient network failures (observed in practice: TLS handshake timeouts reaching a public CDN), since this whole path is otherwise fully deterministic. Returns name, for use as HTTPStorage.ImageRelativePath.

func GetCACertPEM

func GetCACertPEM() []byte

GetCACertPEM returns the PEM encoding of the harness's own CA certificate -- the one that signed the image server's HTTPS certificate (see GetImageServerHTTPSPort). Pass it via HTTPStorage.HTTPSTrustedCACertsPEM to trust downloads from the built-in image server over HTTPS.

func GetCPUsParameterValue

func GetCPUsParameterValue() uint8

GetCPUsParameterValue returns the value set for the CPUs parameter.

func GetControllerEncryptCertPEM

func GetControllerEncryptCertPEM() []byte

GetControllerEncryptCertPEM returns the PEM encoding of the controller's current ECDH certificate (see RotateControllerEncryptCert). The device receives the same certificate via /certs as CERT_TYPE_CONTROLLER_ECDH_EXCHANGE.

func GetControllerHostname

func GetControllerHostname() string

GetControllerHostname returns the controller hostname (stored inside /config/server)

func GetControllerIPv4

func GetControllerIPv4() net.IP

GetControllerIPv4 returns the controller (Adam) IPv4 address.

func GetControllerIPv6

func GetControllerIPv6() net.IP

GetControllerIPv6 returns the controller (Adam) IPv6 address and subnet associated with the container's default IPv6 route, if present.

func GetControllerPort

func GetControllerPort() uint16

GetControllerPort returns the port number on which the controller listens.

func GetControllerSigningCertPEM

func GetControllerSigningCertPEM() []byte

GetControllerSigningCertPEM returns the PEM encoding of the certificate the controller currently uses to sign API responses (see RotateControllerSigningCert). The device receives the same certificate via /certs as CERT_TYPE_CONTROLLER_SIGNING.

func GetDiskSizeMiBParameterValue

func GetDiskSizeMiBParameterValue() uint32

GetDiskSizeMiBParameterValue returns the value set for the DiskSizeMiB parameter.

func GetEVEVersionParameterValue

func GetEVEVersionParameterValue() string

GetEVEVersionParameterValue returns the value set for the EVE version parameter.

func GetImageServerHTTPSPort

func GetImageServerHTTPSPort() uint16

GetImageServerHTTPSPort returns the port of the HTTPS listener that serves the same directory as the HTTP image server (see GetCACertPEM for the CA certificate trusting it).

func GetImageServerIPv4

func GetImageServerIPv4() net.IP

GetImageServerIPv4 returns the IPv4 address of the HTTP image server.

func GetImageServerPort

func GetImageServerPort() uint16

GetImageServerPort returns the port of the HTTP image server.

func GetImageServerSFTPPort

func GetImageServerSFTPPort() uint16

GetImageServerSFTPPort returns the port of the SFTP server that serves the same directory as the HTTP image server (see DefaultSFTPUsername/Password).

func GetRAMSizeMiBParameterValue

func GetRAMSizeMiBParameterValue() uint32

GetRAMSizeMiBParameterValue returns the value set for the RAMSizeMiB parameter.

func GetSrcIPv4ForEVEAccess

func GetSrcIPv4ForEVEAccess() net.IP

GetSrcIPv4ForEVEAccess returns the IPv4 address used as the source IP when a test communicates with EVE management services or EVE applications. This is exposed to allow network-model firewall rules (when enabled) to permit traffic between the test environment and EVE/app endpoints.

func GetSrcIPv4ForInternetAccess

func GetSrcIPv4ForInternetAccess() net.IP

GetSrcIPv4ForInternetAccess returns the first non-link-local IPv4 address of the interface connecting container with the docker network. This IP should be used as the source IP when tests need to access the Internet from the evetest container.

func GetSrcIPv6ForEVEAccess

func GetSrcIPv6ForEVEAccess() net.IP

GetSrcIPv6ForEVEAccess returns the IPv6 address used as the source IP when a test communicates with EVE management services or EVE applications. This is exposed to allow network-model firewall rules (when enabled) to permit traffic between the test environment and EVE/app endpoints.

func GetSrcIPv6ForInternetAccess

func GetSrcIPv6ForInternetAccess() net.IP

GetSrcIPv6ForInternetAccess returns the first global unicast IPv6 address assigned to the interface connecting container with the docker network. This IP should be used as the source address when tests need IPv6 Internet access from the evetest container.

func GetTPMParameterValue

func GetTPMParameterValue() (useTPM bool)

GetTPMParameterValue returns the value set for the TPM parameter.

func GetTestParameter

func GetTestParameter[T any](key string) T

GetTestParameter returns the value of a test parameter with the given key, resolved in the following order:

  1. Value set explicitly by the test-suite
  2. Value provided via environment variable EVETEST_<KEY>
  3. Default value from the parameter definition

The type parameter T must match the parameter’s declared type, otherwise the test will fail.

func IPAddress

func IPAddress(ipAddr string) net.IP

IPAddress converts IP address from string to net.IP

func IPAddressWithPrefix

func IPAddressWithPrefix(cidr string) *net.IPNet

IPAddressWithPrefix parses an IP address with a prefix length (e.g. "172.22.12.10/24") and returns it as *net.IPNet, preserving the host address. Unlike IPSubnet, the host bits are not masked.

func IPSubnet

func IPSubnet(ipSubnet string) *net.IPNet

IPSubnet converts IP subnet from string to *net.IPNet

func LocalLiveImageRequested

func LocalLiveImageRequested() bool

LocalLiveImageRequested reports whether the operator selected the live transport (EVETEST_EVE_LIVE_IMAGE). An unparsable value counts as requested, so the caller surfaces the same error resolveLocalLiveImage would rather than silently treating a typo as "off".

func Logger

func Logger() *logrus.Logger

Logger returns the logrus logger associated with the current test harness.

Tests should use this logger for all test-related logging so that output is consistently formatted and integrated with the harness lifecycle (artifacts, verbosity settings, etc.)

func MACAddress

func MACAddress(macAddr string) net.HardwareAddr

MACAddress converts MAC address from string to net.HardwareAddr

func ReadAllPublications

func ReadAllPublications[T any](d *EdgeDevice, fromAgent string,
	persistent bool) ([]T, error)

ReadAllPublications retrieves all messages from a pub-sub topic published by the specified device and agent (microservice).

Parameters:

  • d: the EdgeDevice handle to read from
  • fromAgent: the name of the agent/microservice publishing the topic

Returns a slice of values of type T representing all messages from the topic, or an error if reading or unmarshaling fails.

func ReadPublication

func ReadPublication[T any](d *EdgeDevice, fromAgent string, persistent bool,
	key string, output *T) error

ReadPublication retrieves a single message from a pub-sub topic published by the specified device and agent (microservice).

Parameters:

  • d: the EdgeDevice handle to read from
  • fromAgent: the name of the agent/microservice publishing the topic
  • key: identifies the specific message within the topic to fetch
  • output: pointer to a value of type T to unmarshal the message into

Returns an error if the message does not exist yet (e.g. before the agent has first published it), cannot be read, or does not unmarshal into T. Callers waiting for a message to appear should poll until the error clears.

func RotateControllerEncryptCert

func RotateControllerEncryptCert(deviceConfigs ...*EdgeDeviceConfig)

RotateControllerEncryptCert rotates the controller's ECDH certificate and private key -- the material every evetest cipher context is derived from -- and migrates the deployed configuration onto it: for every onboarded device it registers a cipher context derived from the new certificate, re-encrypts every cipher block from the retired context to the new one, and re-applies the configuration.

Pass the EdgeDeviceConfig builders the test still holds, and pass the ones it has been applying: configurations are pushed as they are, and EVE config is declarative, so a builder that is a subset of what the device has deployed silently un-deploys the difference. A builder left out keeps blocks encrypted against the retired certificate; a device with no builder given has its harness-held configuration migrated instead, and only if it holds cipher blocks.

Ordering is load-bearing: install the certificates, wait for the device to publish the new one, only then push. Reversed, the device parses a cipher context for a certificate it does not have yet and takes a failed decrypt plus domainmgr's 30s retry. The wait is on EVE's ControllerCert publication because the /certs refetch is only triggered asynchronously, by controllercert_confighash (Adam >= 0.0.81), and a fetch that fails is not re-triggered. The push is required even though nothing else changed: zedagent skips re-parsing cipher contexts while the hash over the set is unchanged.

Not safe to call while devices are still being set up: it mutates the harness-held configuration, which ApplyConfig also reads outside devicesM.

func RotateControllerSigningCert

func RotateControllerSigningCert()

RotateControllerSigningCert rotates the certificate and private key with which the controller signs the AuthContainer that wraps every API response.

Nothing has to be re-encrypted and no configuration has to be pushed: evetest always derives cipher contexts from the controller's ECDH certificate, never from the signing certificate (see encryptCipherData), so object-level encryption is untouched. Use RotateControllerEncryptCert for that.

Unlike RotateControllerEncryptCert this does not wait for the device to pick the new certificate up; the caller decides what to wait for.

Adam is deliberately not restarted: it re-reads the signing certificate and key from disk on every request, whereas a restart would regenerate all keypairs and undo the rotation.

func RunParallel

func RunParallel(numOfWorkers int, workerFunc func(workerIdx int))

RunParallel runs workerFunc concurrently in numOfWorkers goroutines. workerFunc is invoked once per worker and is passed a zero-based workerIdx in the range [0, numOfWorkers). The function blocks until either:

  • all workers complete successfully, or
  • the test is marked as failed, in which case it returns immediately without waiting for the remaining workers.

This enables fail-fast behavior for parallel test execution.

func RunTestSuite

func RunTestSuite(cases ...TestCase)

RunTestSuite executes all variants of all test cases using t.Run. Each variant is executed as a subtest with its own parameter values.

Environment variables such as EVETEST_SUITE_MAX_FAILURES may be used to control suite-wide execution behavior (e.g., early termination on excessive failures).

func Setup

func Setup(requirements ...Requirement)

Setup processes the given requirements. When it returns successfully, all requirements are satisfied and the EVE device(s) is/are powered on and onboarded into the controller. Any failure during setup results in the test being failed or skipped.

func UpdateNetworkModel

func UpdateNetworkModel(netModel *api.NetworkModel)

UpdateNetworkModel updates the current network model, enforcing that device network ports cannot change at runtime.

func WithControllerFault

func WithControllerFault(fault ControllerFault, run func())

WithControllerFault arms a fault, runs the given function, and clears the fault afterwards even if the function fails the test.

Types

type ACLAllowRule

type ACLAllowRule struct {
	Protocol NetworkProtocol
	// Specify either remote subnet or hostname, not both.
	RemoteSubnet   *net.IPNet
	RemoteHostname string
	RemotePort     uint16 // 0 means any
}

ACLAllowRule is a ACL ALLOW rule.

type AppNetworkAdapter

type AppNetworkAdapter interface {
	// contains filtered or unexported methods
}

AppNetworkAdapter identifies types that represent application network adapter configurations.

type ApplicationImageStorage

type ApplicationImageStorage interface {
	// contains filtered or unexported methods
}

ApplicationImageStorage defines a generic interface for datastore configurations that can be converted into EVE protobuf representation.

type ApplicationInstanceConfig

type ApplicationInstanceConfig struct {
	DisplayName        string
	Activate           bool
	ProfileList        []string
	Image              ApplicationImageStorage
	VirtualizationMode eveconfig.VmMode
	CPUs               uint
	MemoryBytes        uint64
	DiskBytes          uint64
	EnableVNC          bool
	VNCDisplay         uint
	VNCPassword        string
	// RemoteConsole gates VNC access under the Kubevirt hypervisor: unlike
	// KVM (where EnableVNC/VNCDisplay/VNCPassword directly configure a raw
	// QEMU VNC socket reachable on the device's uplink IP), Kubevirt exposes
	// VNC via zedkube's virtctl-based proxy, which only binds the VNC port
	// on 127.0.0.1 and is gated by this field, not by EnableVNC. VNCDisplay
	// still selects the port (5900+VNCDisplay); VNCPassword is not enforced
	// -- see pkg/pillar/docs/vnc-workflows.md. zedkube only starts/stops the
	// proxy on a *change* of this field, and only one remote-console session
	// is allowed on the device at a time.
	RemoteConsole       bool
	DisableLogs         bool
	UserData            string
	NetworkAdapters     []AppNetworkAdapter
	EnforceNetIntfOrder bool
	// IOAdapters are I/O devices other than network adapters (a GPU, a USB
	// controller, a serial port, ...) directly assigned to the application,
	// each referencing a PhysicalIO entry added with
	// EdgeDeviceConfig.AddPhysicalIO.
	IOAdapters []IOAdapterConfig
	// Mounts are additional volumes attached to the application alongside its
	// root disk (built from Image/DiskBytes). Each entry references an
	// existing, independently created volume (see EdgeDeviceConfig.AddVolume
	// / AddBlankVolume) -- AddApplication/UpdateApplication only wire up (or
	// rewire) the app's VolumeRefList to point at it; they never create or
	// remove the volume itself.
	Mounts []MountConfig
}

ApplicationInstanceConfig wraps configuration for a single application deployed on EVE.

type AuthMethod

type AuthMethod interface {
	// contains filtered or unexported methods
}

AuthMethod is a marker interface for application authentication methods.

type AwsS3Bucket

type AwsS3Bucket struct {
	ImageFormat       eveconfig.Format
	ImageSHA256       string
	MaxDownloadBytes  uint64
	ImageRelativePath string
	Region            string
	Bucket            string
	AccessKeyID       string
	SecretAccessKey   string
}

AwsS3Bucket defines path to application image stored inside AWS S3. https://<Bucket>.s3.<Region>.amazonaws.com/<ImageRelativePath>

type AzureBlob

type AzureBlob struct {
	ImageFormat       eveconfig.Format
	ImageSHA256       string
	MaxDownloadBytes  uint64
	ImageRelativePath string
	AccountName       string
	AccountKey        string
	Container         string
}

AzureBlob defines path to application image stored inside Azure Blob. https://<AccountName>.blob.core.windows.net/<Container>/<ImageRelativePath>

type BaseOSDatastoreType

type BaseOSDatastoreType int

BaseOSDatastoreType selects how EdgeDevice.UpgradeEVE delivers the target EVE rootfs to the device.

const (
	// BaseOSDatastoreHTTP has evetest extract the raw rootfs image from the
	// locally-pulled EVE docker image and serve it over evetest's own
	// embedded HTTP image server. This is the traditional/default path and
	// works even when the target image is not (or cannot be) hosted on a
	// registry reachable from the device.
	BaseOSDatastoreHTTP BaseOSDatastoreType = iota
	// BaseOSDatastoreOCI has EVE pull the target rootfs directly from the
	// same OCI registry the target image was tagged in (e.g. Docker Hub),
	// using a container-registry Datastore. evetest still pulls the image
	// locally first to determine the target short version, but skips
	// extracting the raw rootfs and serving it over HTTP.
	BaseOSDatastoreOCI
)

func (*BaseOSDatastoreType) FromString

func (t *BaseOSDatastoreType) FromString(s string) error

FromString parses a datastore type name string and sets the BaseOSDatastoreType value.

func (BaseOSDatastoreType) String

func (t BaseOSDatastoreType) String() string

type BondConfig

type BondConfig struct {
	LogicalLabel  string
	InterfaceName string
	MemberLabels  []string // Logical labels of aggregated PhysicalIO adapters.
	BondMode      evecommon.BondMode
	MIIMonitor    *eveconfig.MIIMonitor
	ARPMonitor    *eveconfig.ArpMonitor
	LACPRate      evecommon.LacpRate
	Cost          uint8
	NetworkUUID   uuid.UUID
	StaticIP      net.IP // use only in combination with StaticNetworkConfig
	SharedLabels  []string
	Usage         evecommon.PhyIoMemberUsage
}

BondConfig represents a bond (link aggregation) interface.

type CSRProfile

type CSRProfile struct {
	// X.509 Distinguished Name (DN) attributes.
	CommonName         string // CN
	Organization       string // O
	OrganizationalUnit string // OU
	Country            string // C
	State              string // ST
	Locality           string // L

	// X.509 Subject Alternative Name (SAN) attributes.
	SanDNS    []string
	SanIPs    []net.IP
	SanURIs   []string
	SANEmails []string

	// Certificate renewal settings.
	// Percentage of the certificate validity period after which
	// the device should attempt renewal (e.g., 80 = renew after 80%).
	RenewPeriodPercent uint8

	KeyType       eveconfig.KeyType
	HashAlgorithm eveconfig.HashAlgorithm
}

CSRProfile : Certificate Signing Request (CSR) configuration profile. Defines subject identity, extensions, cryptographic parameters, and renewal behavior for certificate enrollment.

type CellularNetworkConfig

type CellularNetworkConfig struct {
	// "DHCP" is not quite accurate here since most of the time we get IP configuration
	// from the cellular network via PDP context activation, not traditional DHCP.
	// But other than that, all the configuration parameters are the same, so we reuse
	// DHCPNetworkConfig
	DHCPNetworkConfig
	// SIM card slot to which this configuration applies.
	// 0 - unspecified (apply to currently activated or the only available)
	// 1 - config for SIM card in the first slot
	// 2 - config for SIM card in the second slot
	// etc.
	SIMSlot uint8
	// Access Point Network for the default bearer.
	APN string
	// The IP addressing type to use for the default bearer.
	IPType evecommon.CellularIPType
	// Authentication protocol used for the default bearer.
	AuthProtocol evecommon.CellularAuthProtocol
	// User credentials for the default bearer (when required).
	UserCredentials UsernamePasswordAuth
	// The set of cellular network operators that modem should preferably try to register
	// and connect into.
	// Network operator should be referenced by PLMN (Public Land Mobile Network) code.
	PreferredPLMNs []string
	// The list of preferred Radio Access Technologies (RATs) to use for connecting
	// to the network.
	PreferredRATs []evecommon.RadioAccessTechnology
	// If true, then modem will avoid connecting to networks with roaming.
	ForbidRoaming bool
	// Access Point Network for the attach (aka initial) bearer.
	AttachAPN string
	// The IP addressing type to use for the attach bearer.
	AttachIPType evecommon.CellularIPType
	// Authentication protocol used for the attach bearer.
	AttachAuthProtocol evecommon.CellularAuthProtocol
	// User credentials for the attach bearer (when required).
	AttachUserCredentials UsernamePasswordAuth
	// Enable probing to detect broken connection.
	EnableProbing bool
	// User-defined connectivity probing method.
	UserDefinedProbe pillartypes.ConnectivityProbe
	// Some LTE modems have GNSS receiver integrated and can be used
	// for device location tracking.
	// Enable this option to have location info periodically obtained
	// from this modem and published by wwan microservice via topic WwanLocationInfo.
	LocationTracking bool
}

CellularNetworkConfig represents a cellular network configuration.

type ClientCertAuth

type ClientCertAuth struct {
	KeyPEM string
}

ClientCertAuth represents client certificate–based authentication.

type ClusterApplicationInstanceConfig

type ClusterApplicationInstanceConfig struct {
	ApplicationInstanceConfig

	// DesignatedNodeName is the device name of the cluster node where
	// the application should be placed. This field is mandatory.
	DesignatedNodeName string

	// Affinity determines how strictly the designated node preference
	// is enforced. The default zero value is AFFINITY_TYPE_PREFERRED.
	Affinity eveconfig.AffinityType
}

ClusterApplicationInstanceConfig wraps ApplicationInstanceConfig with cluster-specific fields required when deploying an application across a cluster of edge nodes.

type ClusterBlankVolumeConfig

type ClusterBlankVolumeConfig struct {
	DisplayName string
	SizeBytes   uint64
	// ClearText creates the volume outside the vault. See
	// EdgeDeviceConfig.AddBlankVolume.
	ClearText bool
	// DesignatedNodeName sets the volume's designated node. Empty leaves it
	// unset, so each node that gets the volume treats itself as its owner.
	DesignatedNodeName string
	// GenerationCount is the volume's generation. A controller raises it to
	// rebuild a volume from scratch. A test can start a volume above zero to
	// stand for one that was rebuilt, whose earlier generation is gone.
	GenerationCount int64
	// NodeNames lists the nodes whose config carries the volume. Empty means
	// every node. A controller need not send a volume to every node of a
	// cluster, and a test can use this to do the same.
	NodeNames []string
	// VolumeUUID is the volume's UUID. Zero generates one. A test sets it to
	// create objects under the UUID before the volume is sent.
	VolumeUUID uuid.UUID
}

ClusterBlankVolumeConfig describes a standalone blank volume for EdgeClusterConfig.AddBlankVolume.

type ClusterNode

type ClusterNode struct {
	DevName          string
	ClusterIP        *net.IPNet
	ClusterInterface string
	BootstrapNode    bool

	// TieBreaker marks this node as the cluster tie-breaker: the third
	// node of an HA cluster, which exists only to give etcd a quorum
	// vote. EVE keeps it cordoned, runs no workloads on it and places no
	// Longhorn replicas there, which also lowers the replica count of
	// the storage class used for new volumes.
	//
	// Leave it false on every node to configure no tie-breaker at all.
	TieBreaker bool
}

ClusterNode identifies one node participating in the cluster. ClusterIP is the IP address (with prefix) assigned to this node on the cluster interface. ClusterInterface is the logical label of the physical port used for intra-cluster communication on this node. Exactly one node should have BootstrapNode set to true — its ClusterIP is used as the join server IP for all nodes. At most one node may have TieBreaker set to true.

type ControllerFault

type ControllerFault = controller.FaultRule

ControllerFault describes which of the requests a device sends to the controller should fail, and how. See ArmControllerFault.

type CrashWatch

type CrashWatch struct {
	// contains filtered or unexported fields
}

CrashWatch tells whether the device crashed since the watch started: an agent died with a fatal, the watchdog gave up on a process, or the device rebooted without being asked to. A test waiting for something a crash would prevent polls Reason to fail at once, naming the cause, instead of waiting out its timeout.

func (*CrashWatch) Reason

func (w *CrashWatch) Reason() string

Reason returns why the device is known to have crashed, or "" while it is not known to have. The serial console tells within seconds, whatever becomes of the device afterwards; the other signs register only once the device is back up and talking to the controller, since a fatal log line travels with the next log upload and an unexpected reboot is recognized from the boot time in the device's info messages.

func (*CrashWatch) Stop

func (w *CrashWatch) Stop()

Stop ends the watch.

type DHCPNetworkConfig

type DHCPNetworkConfig struct {
	NetworkType       evecommon.NetworkType
	MTU               uint16
	NTPServers        []string
	IgnoreNTPFromDHCP bool
	// DNSServers are static DNS server IPs to configure in addition to (or
	// instead of) the DHCP-provided DNS servers. When IgnoreDNSFromDHCP is
	// false (the default), these are appended to the DHCP-provided servers.
	// When IgnoreDNSFromDHCP is true, only these are used.
	DNSServers []net.IP
	// IgnoreDNSFromDHCP controls whether DHCP-provided DNS servers are ignored.
	// When true, only the statically configured DNSServers are used.
	// Corresponds to DhcpOptionsIgnore.DnsConfigExclusively in the EVE API.
	IgnoreDNSFromDHCP bool
	ProxyConfig       ProxyConfig
}

DHCPNetworkConfig represents a network configuration for Ethernet interface, with DHCP used for IP assignment.

type DNSLogMatch

type DNSLogMatch struct {
	VirtualNetAdapter string // logical label
	NetworkInstance   uuid.UUID
	// NotBefore and NotAfter relates to DnsRequest.requestTime
	NotBefore time.Time
	NotAfter  time.Time
}

DNSLogMatch defines filtering criteria for matching application DNS logs.

type DirectlyAssignedNetworkAdapter

type DirectlyAssignedNetworkAdapter struct {
	LogicalLabel   string
	InterfaceOrder *uint32
}

DirectlyAssignedNetworkAdapter represents network adapter directly assigned to an application (e.g. using PCI passthrough).

type DiskLayoutType

type DiskLayoutType int

DiskLayoutType is the desired ZFS/RAID disk-array layout for EdgeDeviceConfig.SetDisksLayout.

const (
	// DiskLayoutUnspecified leaves EVE's automatic (default) disk selection
	// in place -- no explicit layout is configured.
	DiskLayoutUnspecified DiskLayoutType = iota
	// DiskLayoutRAID1 mirrors 2 disks.
	DiskLayoutRAID1
	// DiskLayoutRAID10 stripes 2 RAID1 mirrors (4 disks total).
	DiskLayoutRAID10
)

type DisksLayout

type DisksLayout struct {
	LayoutType DiskLayoutType
	// OfflineDisks marks disk slots as ZFS_OFFLINE.
	OfflineDisks []uint
	// UnusedDisks marks disk slots as UNUSED (removed from the array).
	UnusedDisks []uint
	// ReplaceDisks requests replacing the disk currently at the given slot
	// with a spare disk, selected in order from the device's extra disks
	// beyond what the layout itself occupies (e.g. for RAID1, slot 0's
	// replacement is the device's 3rd extra disk).
	ReplaceDisks []uint
}

DisksLayout describes the desired ZFS/RAID disk-array layout of a device's extra (non-boot) virtio disks (see RequireEdgeDevice.ExtraDisks), mirroring eden's own disks-layout model (pkg/device/disksLayout.go in the eden repo, adapted here since evetest's broker only ever attaches virtio disks -- see DiskName). Disk slot indices below (0..3 for RAID10, 0..1 for RAID1) refer to positions within the layout, not the device's overall extra-disk list.

type DockerContainer

type DockerContainer struct {
	Domain    string // default: "index.docker.io"
	ImageName string
	Tag       string

	// TrustedCACertsPEM lists CA certificates EVE should trust when connecting
	// to Domain over HTTPS, in addition to its normal trust store -- e.g. the
	// harness's own CA (GetCACertPEM) when Domain points at evetest's embedded
	// OCI registry (see PushDockerImageToLocalRegistry), or an enterprise
	// registry's own internal CA. Mirrors HTTPStorage.HTTPSTrustedCACertsPEM;
	// leave unset for a registry already trusted via the normal chain (e.g.
	// Docker Hub).
	TrustedCACertsPEM []string
}

DockerContainer defines path to application image stored inside a docker image registry.

func PushDockerImageToLocalRegistry

func PushDockerImageToLocalRegistry(imageName string) (DockerContainer, error)

PushDockerImageToLocalRegistry copies a Docker image -- pulling it first if not already present locally -- from the local Docker daemon into evetest's own embedded OCI registry, and returns the DockerContainer fields that point an EVE datastore at that copy.

This is what lets an OCI/container datastore be exercised (an EVE upgrade via BaseOSDatastoreOCI, or a DockerContainer volume/app image) without the image under test already being published to a real, externally reachable registry: evetest re-serves whatever the local Docker daemon has under imageName ("<repo>:<tag>", e.g. as returned by utils.EVEDockerImageName) as a datastore of its own.

func PushImageWithMissingLayersToLocalRegistry

func PushImageWithMissingLayersToLocalRegistry(repo, tag string, numLayers int) (
	image DockerContainer, layerDigests []string, err error)

PushImageWithMissingLayersToLocalRegistry publishes, under repo:tag in evetest's embedded OCI registry, an image of numLayers layers of which only the manifest and the config blob are uploaded: the layer blobs never are. A pull of the image resolves the tag and fetches the manifest and the config without trouble, then fails on every layer with the registry's BLOB_UNKNOWN. That is the shape of a registry, or a pull-through mirror, that has lost or never received the blobs a manifest refers to, and of a registry that becomes unreachable right after serving the manifest: every layer fails on its own, and from every management port the downloader tries it from.

Every layer is a few bytes of random gzip, so no two images published this way share a digest and nothing pulled earlier can stand in for a layer. The layers' digests are returned as volumemgr names them (lowercase hex, no "sha256:" prefix), in manifest order, for a test to recognize them in what EVE reports. The returned DockerContainer points an EVE datastore at the image, as PushDockerImageToLocalRegistry does.

type EdgeCluster

type EdgeCluster struct {
	// contains filtered or unexported fields
}

EdgeCluster represents a cluster of edge devices and provides operations that span across all cluster nodes (e.g. applying config, waiting for application deployment, running scripts inside cluster-scheduled apps).

func NewEdgeCluster

func NewEdgeCluster(clusterName string) *EdgeCluster

NewEdgeCluster creates an EdgeCluster handle for the given cluster name. The typical workflow is to create the handle, then call ApplyConfig to push the cluster configuration to all devices, and finally interact with the cluster (e.g. WaitUntilNodesAreReady, deploy applications, etc.).

func (*EdgeCluster) ActivateApplication

func (ec *EdgeCluster) ActivateApplication(appUUID uuid.UUID, waitUntilActivated bool,
	timeout time.Duration)

ActivateApplication activates the specified application across the cluster. The activate flag is set on all devices, but the wait (if requested) is performed only on the device hosting the application.

func (*EdgeCluster) ApplyConfig

func (ec *EdgeCluster) ApplyConfig(clusterConfig *EdgeClusterConfig,
	waitUntilFetched, waitUntilConfirmed bool, excludeFromWait ...string)

ApplyConfig applies the configuration from the given EdgeClusterConfig to all cluster devices in parallel. The waitUntilFetched and waitUntilConfirmed arguments are forwarded to each device's ApplyConfig; see EdgeDevice.ApplyConfig for their semantics. excludeFromWait names devices the config is still pushed to, but without waiting on either flag -- e.g. a device deliberately powered off for the test, which would otherwise block waitUntilFetched/waitUntilConfirmed forever. The set of cluster devices is collected from the EdgeClusterConfig nodes and stored for use by subsequent methods.

func (*EdgeCluster) DeactivateApplication

func (ec *EdgeCluster) DeactivateApplication(appUUID uuid.UUID, waitUntilDeactivated bool,
	timeout time.Duration)

DeactivateApplication deactivates the specified application across the cluster. The activate flag is cleared on all devices, but the wait (if requested) is performed only on the device hosting the application.

func (*EdgeCluster) FindDeviceHostingApp

func (ec *EdgeCluster) FindDeviceHostingApp(appUUID uuid.UUID,
	timeout time.Duration, excludeDevNames ...string) *EdgeDevice

FindDeviceHostingApp finds the cluster device that hosts the given application. It returns the device the newest stored ZInfoKubeCluster report places the app on (matched by display name in EveApps or EveVmApps), or else waits for a new report that does.

Only the newest report is trusted: only the stats leader publishes cluster info, and a node that loses the lease stops publishing without retracting its last report, so an older report can name a node the app has left.

Pass excludeDevNames to wait for the app to move OFF the named devices, which is what a failover test needs. Excluded devices are skipped as a source of cluster info and are never returned, so the answer can only come from a device that is still up.

func (*EdgeCluster) GetAppLogs

func (ec *EdgeCluster) GetAppLogs(appUUID uuid.UUID, match LogMsgMatch) []LogMsg

GetAppLogs collects log messages for the specified application from all cluster devices (since the app may have migrated between nodes during its lifespan) and returns them sorted by timestamp.

func (*EdgeCluster) PurgeApplication

func (ec *EdgeCluster) PurgeApplication(appUUID uuid.UUID, volumeGen VolumeGenerationPolicy,
	waitUntilPurged bool, timeout time.Duration, excludeDevNames ...string)

PurgeApplication purges the specified application across the cluster. The purge counter is incremented on all devices, but the wait (if requested) is performed only on the device hosting the application. See EdgeDevice.PurgeApplication for volumeGen.

Name in excludeDevNames any node that must not be taken for the host, such as one the caller has powered off: the cluster info it published before going down still names it as the host, and waiting there never completes.

func (*EdgeCluster) RebootApplication

func (ec *EdgeCluster) RebootApplication(appUUID uuid.UUID, waitUntilRebooted bool,
	timeout time.Duration)

RebootApplication reboots the specified application across the cluster. The reboot counter is incremented on all devices, but the wait (if requested) is performed only on the device hosting the application.

func (*EdgeCluster) RunShellScriptInsideApp

func (ec *EdgeCluster) RunShellScriptInsideApp(appUUID uuid.UUID, auth AuthMethod,
	script string, timeout time.Duration,
	stdoutWatchdogTimeout time.Duration) (stdout, stderr string, err error)

RunShellScriptInsideApp locates the cluster node hosting the specified application and executes a shell script inside it over SSH.

func (*EdgeCluster) WaitForFreshNodesReady

func (ec *EdgeCluster) WaitForFreshNodesReady(timeout time.Duration)

WaitForFreshNodesReady is WaitUntilNodesAreReady, except that it counts only reports that arrive after the call. Use it after a node reboots or is powered off and on. The leader's report can show a powered-off node Ready until Kubernetes marks it NotReady, so call it only once an outage has lasted that long.

func (*EdgeCluster) WaitUntilAppIsHostedOn

func (ec *EdgeCluster) WaitUntilAppIsHostedOn(appUUID uuid.UUID, devName string,
	timeout time.Duration) *EdgeDevice

WaitUntilAppIsHostedOn waits until the newest cluster info report places the app on devName and on no other node, and returns that device.

Use it to verify placement after the app has moved. Like FindDeviceHostingApp it starts from the newest stored report, because EVE sends cluster info only when it changes, so a placement that settled before the call may never be reported again. Requiring the app on devName alone rides out the window where the VMI being replaced is still listed next to its successor.

func (*EdgeCluster) WaitUntilAppIsRunning

func (ec *EdgeCluster) WaitUntilAppIsRunning(
	appUUID uuid.UUID, timeoutExcludingDownload time.Duration)

WaitUntilAppIsRunning waits until the specified application is running on one of the cluster nodes. It first locates the destination device via cluster info, then delegates to that device's WaitUntilAppIsRunning.

func (*EdgeCluster) WaitUntilClusterInfoSatisfies

func (ec *EdgeCluster) WaitUntilClusterInfoSatisfies(timeout time.Duration,
	freshOnly bool, description string, pred func(*eveinfo.ZInfoKubeCluster) bool)

WaitUntilClusterInfoSatisfies waits until any device in the cluster reports a ZInfoKubeCluster satisfying pred. Only the elected leader node publishes cluster info, and which device that is can change over the cluster's lifetime (e.g. after a quorum recovery promotes a different node), so every device is watched in parallel and the function succeeds as soon as any single one satisfies pred. It fails if the timeout expires before that happens. With freshOnly, reports received before the call are ignored. description is used only for logging.

func (*EdgeCluster) WaitUntilNodesAreReady

func (ec *EdgeCluster) WaitUntilNodesAreReady(timeout time.Duration)

WaitUntilNodesAreReady waits until any device in the cluster reports all cluster nodes as Ready via ZInfoKubeCluster. Only the elected leader node publishes cluster info, so the function succeeds as soon as any single device reports all nodes ready. It fails if the timeout expires before that happens.

It accepts the last report already received. After a node reboots, that report can predate the reboot and still show the node Ready, so use WaitForFreshNodesReady there.

type EdgeClusterConfig

type EdgeClusterConfig struct {
	ClusterID uuid.UUID
	Token     string // plaintext join token
	// contains filtered or unexported fields
}

EdgeClusterConfig manages device configurations for a cluster of edge nodes. Methods that create UUID-identified objects (networks, network instances, applications, etc.) generate the UUID once and apply the same object to every node. Encrypted data (e.g. cluster join token, datastore credentials) is encrypted individually per device because each device has its own encryption key. For per-device customization, use GetDeviceConfig.

func NewEdgeClusterConfig

func NewEdgeClusterConfig(
	clusterType eveconfig.ClusterType, nodes ...ClusterNode) *EdgeClusterConfig

NewEdgeClusterConfig constructs an EdgeClusterConfig. It creates an EdgeDeviceConfig for each node, generates a shared cluster UUID and join token, and sets the cluster configuration on every device. The join token is encrypted individually per device.

func (*EdgeClusterConfig) AddApplication

func (cc *EdgeClusterConfig) AddApplication(
	config ClusterApplicationInstanceConfig) uuid.UUID

AddApplication adds an application to all devices. The same UUIDs (app, volume, content tree, datastore) are used across all devices. DesignatedNodeName is resolved to the device UUID and set on the AppInstanceConfig, Volume and ContentTree, along with the Affinity field on the app.

func (*EdgeClusterConfig) AddBlankVolume

func (cc *EdgeClusterConfig) AddBlankVolume(config ClusterBlankVolumeConfig) uuid.UUID

AddBlankVolume adds one standalone blank volume, under a single UUID, to the configs of the selected cluster nodes, and returns that UUID.

func (*EdgeClusterConfig) AddNetwork

func (cc *EdgeClusterConfig) AddNetwork(netConfig NetworkConfig) uuid.UUID

AddNetwork adds a network configuration to all devices. The same UUID is used across all devices.

func (*EdgeClusterConfig) AddNetworkAdapter

func (cc *EdgeClusterConfig) AddNetworkAdapter(config NetworkAdapterConfig)

AddNetworkAdapter adds a network adapter to all devices.

func (*EdgeClusterConfig) AddNetworkInstance

func (cc *EdgeClusterConfig) AddNetworkInstance(config NetworkInstanceConfig) uuid.UUID

AddNetworkInstance adds a network instance to all devices. The same UUID is used across all devices.

func (*EdgeClusterConfig) AddNode

func (cc *EdgeClusterConfig) AddNode(node ClusterNode)

AddNode extends a built cluster configuration with one more node, which is how a test grows a running cluster. The device must already be onboarded; call EdgeCluster.ApplyConfig afterwards to push the result.

The new node is cloned from the first node, so it shares its networks, adapters, network instances and applications under the same UUIDs -- which is what makes it a peer rather than a lookalike. Only the cluster IP and the per-device secrets are rebuilt. Two consequences:

  • Only the first node is the template, so a GetDeviceConfig customization made on another node is not carried over.
  • The new node is never the bootstrap node, and never the tie-breaker: that has to be named at construction, since every node is told which node it is.

func (*EdgeClusterConfig) AddSCEPProfile

func (cc *EdgeClusterConfig) AddSCEPProfile(profile SCEPProfile)

AddSCEPProfile adds a SCEP profile to all devices. Encryption is performed individually per device.

func (*EdgeClusterConfig) AddVLANSubinterface

func (cc *EdgeClusterConfig) AddVLANSubinterface(config VLANSubinterfaceConfig)

AddVLANSubinterface adds a VLAN sub-interface to all devices.

func (*EdgeClusterConfig) DeleteApplication

func (cc *EdgeClusterConfig) DeleteApplication(appUUID uuid.UUID)

DeleteApplication removes an application from all devices.

func (*EdgeClusterConfig) DeleteNetwork

func (cc *EdgeClusterConfig) DeleteNetwork(networkUUID uuid.UUID)

DeleteNetwork removes a network from all devices.

func (*EdgeClusterConfig) DeleteNetworkAdapter

func (cc *EdgeClusterConfig) DeleteNetworkAdapter(logicalLabel string)

DeleteNetworkAdapter removes a network adapter from all devices.

func (*EdgeClusterConfig) DeleteNetworkInstance

func (cc *EdgeClusterConfig) DeleteNetworkInstance(niUUID uuid.UUID)

DeleteNetworkInstance removes a network instance from all devices.

func (*EdgeClusterConfig) DeleteSCEPProfile

func (cc *EdgeClusterConfig) DeleteSCEPProfile(profileName string)

DeleteSCEPProfile removes a SCEP profile from all devices.

func (*EdgeClusterConfig) DeleteVLANSubinterface

func (cc *EdgeClusterConfig) DeleteVLANSubinterface(logicalLabel string)

DeleteVLANSubinterface removes a VLAN sub-interface from all devices.

func (*EdgeClusterConfig) GetDeviceConfig

func (cc *EdgeClusterConfig) GetDeviceConfig(devName string) *EdgeDeviceConfig

GetDeviceConfig returns the EdgeDeviceConfig for a specific device, allowing per-device customization.

func (*EdgeClusterConfig) SetConfigProperties

func (cc *EdgeClusterConfig) SetConfigProperties(configProps *pillartypes.ConfigItemValueMap)

SetConfigProperties sets configuration properties on all devices.

func (*EdgeClusterConfig) SetLPS

func (cc *EdgeClusterConfig) SetLPS(config LPSConfig)

SetLPS sets the Local Profile Server configuration on all devices.

func (*EdgeClusterConfig) SetNativeK8SOrchestration

func (cc *EdgeClusterConfig) SetNativeK8SOrchestration(enabled bool)

SetNativeK8SOrchestration enables native Kubernetes orchestration of user workloads ("base mode"), the opt-in that replaced CLUSTER_TYPE_K3S_BASE: EVE then serves EVE-API-scheduled and natively-applied workloads side by side.

The EVE API only accepts it on CLUSTER_TYPE_REPLICATED_STORAGE, so any other type fails here rather than being rejected by EVE. AddNode inherits it.

func (*EdgeClusterConfig) SetRegistrationManifest

func (cc *EdgeClusterConfig) SetRegistrationManifest(manifestYAML []byte)

SetRegistrationManifest attaches a manifest for the cluster to apply to itself once up -- how a controller registers an EVE-K cluster.

It shares the join token's cipher block, so this re-encrypts that block for every node. Only the bootstrap node acts on it: zedkube writes /persist/vault/manifests/registration.yaml, kube-init stages it into the k3s server-manifests dir, and k3s applies it as the "persist-registration" AddOn.

Pass nil to remove it. AddNode inherits whatever is set at that point.

func (*EdgeClusterConfig) SetWitness

func (cc *EdgeClusterConfig) SetWitness(witnessIP net.IP)

SetWitness configures the 2-node-HA witness's virtual etcd IP on every device's cluster config. The witness always co-locates with whichever device currently owns JoinServerIp - there is no separate placement field (matches config.EdgeNodeClusterWitness's design).

func (*EdgeClusterConfig) UpdateApplication

func (cc *EdgeClusterConfig) UpdateApplication(
	appUUID uuid.UUID, newConfig ClusterApplicationInstanceConfig)

UpdateApplication updates an application on all devices. DesignatedNodeName is resolved to the device UUID and updated on the AppInstanceConfig, Volume and ContentTree, along with the Affinity field.

func (*EdgeClusterConfig) UpdateNetwork

func (cc *EdgeClusterConfig) UpdateNetwork(networkUUID uuid.UUID, newConfig NetworkConfig)

UpdateNetwork updates an existing network on all devices.

func (*EdgeClusterConfig) UpdateNetworkAdapter

func (cc *EdgeClusterConfig) UpdateNetworkAdapter(config NetworkAdapterConfig)

UpdateNetworkAdapter updates a network adapter on all devices.

func (*EdgeClusterConfig) UpdateNetworkInstance

func (cc *EdgeClusterConfig) UpdateNetworkInstance(
	niUUID uuid.UUID, newConfig NetworkInstanceConfig)

UpdateNetworkInstance updates a network instance on all devices.

func (*EdgeClusterConfig) UpdateSCEPProfile

func (cc *EdgeClusterConfig) UpdateSCEPProfile(profile SCEPProfile)

UpdateSCEPProfile updates a SCEP profile on all devices.

func (*EdgeClusterConfig) UpdateVLANSubinterface

func (cc *EdgeClusterConfig) UpdateVLANSubinterface(config VLANSubinterfaceConfig)

UpdateVLANSubinterface updates a VLAN sub-interface on all devices.

type EdgeDevice

type EdgeDevice struct {
	// contains filtered or unexported fields
}

EdgeDevice represents a single onboarded EVE device and provides operations to manage its lifecycle, configuration, applications, and runtime state.

func GetAllEdgeDevices

func GetAllEdgeDevices() (devices []*EdgeDevice)

GetAllEdgeDevices returns handles for all EdgeDevices currently known to the test th.

func GetEdgeDevice

func GetEdgeDevice(devName string) *EdgeDevice

GetEdgeDevice returns a handle to an onboarded EdgeDevice identified by devName.

func (*EdgeDevice) ActivateApplication

func (d *EdgeDevice) ActivateApplication(appUUID uuid.UUID, waitUntilActivated bool,
	timeout time.Duration)

ActivateApplication activates the specified application instance.

func (*EdgeDevice) ApplyConfig

func (d *EdgeDevice) ApplyConfig(config *EdgeDeviceConfig, waitUntilFetched bool, waitUntilConfirmed bool)

ApplyConfig applies a device configuration and optionally waits for confirmation that it was received and/or processed by the device.

If waitUntilFetched is true, the function blocks until EVE fetches the new config from the controller. This is reliable even when the config changes the management port, because EVE downloads the config before activating the new port — the wait completes while connectivity is still intact.

If waitUntilConfirmed is true, the function additionally waits until EVE reports LastProcessedConfig >= the config's timestamp in device metrics, which indicates that zedagent has parsed the config and distributed it to other microservices. Do not combine this with configs that change the management port: the device may lose controller connectivity right after applying the change, delaying the metrics publish indefinitely. While a reboot declared with ExpectAdditionalReboots has not been observed yet, the wait is extended by the reboot timeout, since such a config is often confirmed only after the device boots again.

The two flags are evaluated in order: fetch first, then confirm.

func (*EdgeDevice) AttachUSBHub

func (d *EdgeDevice) AttachUSBHub(id, parentHubID string) string

AttachUSBHub plugs a QEMU usb-hub into the running device, into the lowest free port of the hub parentHubID, or of the xHCI controller when that is "", and returns its port path, e.g. "1" or "1.2". Hubs and drives plugged into it name it by id (AttachUSBHub, AttachUSBStorageBehind). The hub is a full-speed USB 1.1 hub with eight ports, so Linux enumerates it and everything below it on the controller's USB 2 bus, and QEMU accepts chains of up to five hubs. Fails the test on error.

Requires RequireCapabilities{CAPABILITY_QMP}.

func (*EdgeDevice) AttachUSBStorage

func (d *EdgeDevice) AttachUSBStorage(id string, sizeBytes uint64) string

AttachUSBStorage plugs a blank USB flash drive of the given size into the running device: a scratch image on the hypervisor host, a raw block node on top of it and a usb-storage device on the VM's xHCI controller (USBControllerBus). id is the QEMU device id, the block node name, the scratch image name and the drive's USB serial number, so that ListUSBDevices finds the drive with FindBySerial(id). Fails the test on error, undoing the steps already done.

The drive goes into the lowest free port of the controller, and a drive re-plugged under the same id goes back into the port it left while that is free (see usbPortAllocator), like a physical re-plug into the same receptacle; a device-model entry claiming it by bus and port thus keeps matching. The controller has four ports on both providers. Returns the port, e.g. "2".

Requires RequireCapabilities{CAPABILITY_QMP}.

func (*EdgeDevice) AttachUSBStorageBehind

func (d *EdgeDevice) AttachUSBStorageBehind(hubID, id string, sizeBytes uint64) string

AttachUSBStorageBehind is AttachUSBStorage into the lowest free port of the hub hubID (see AttachUSBHub) instead of the controller. Returns the drive's port path, e.g. "2.1" for the first port of a hub in port 2.

func (*EdgeDevice) ConsoleOutput

func (d *EdgeDevice) ConsoleOutput() (string, error)

ConsoleOutput returns everything the device has written to its serial console so far in this test. The console log is opened in append mode, so output from earlier boots is still present: this is the only way a test can see what ran before pillar started, which is where storage-init and the offline resizer live. Returns an error rather than failing the test, so a caller can treat missing console output as inconclusive rather than fatal.

func (*EdgeDevice) CreatePersistPartition

func (d *EdgeDevice) CreatePersistPartition(extraDiskIdx uint)

CreatePersistPartition writes a fresh GPT on the extraDiskIdx-th extra disk (see RequireEdgeDevice.ExtraDisks), with a single partition spanning it, typed and named as EVE's /persist.

This is how a test gives a device a /persist on a disk of its own. Pair it with DeletePartition("P3") on the boot disk: EVE locates /persist by partition label across every disk, so with the boot disk's copy gone it adopts this one, and the space freed on the boot disk becomes a tail an in-field repartition can grow into.

The partition is left unformatted, and EVE formats it on the next boot just as it would a factory-fresh disk -- so the filesystem under test is EVE's own, not a lookalike built by the harness.

The device must be powered off (PowerOff), and the test must have declared RequireCapabilities{CAPABILITY_EDIT_DEVICE_DISK}.

func (*EdgeDevice) CreateScratchImage

func (d *EdgeDevice) CreateScratchImage(name string, sizeBytes uint64) string

CreateScratchImage creates a blank, sparse raw disk image of the given size for the device on the hypervisor host and returns its path there, for use in QMP commands such as blockdev-add. name identifies the image within the device for DeleteScratchImage; it must follow QEMU's id rules (a letter followed by up to 30 letters, digits, '-' or '_'), so that it can double as block node name and device id. The image is removed with the device at the latest.

Requires RequireCapabilities{CAPABILITY_QMP}.

func (*EdgeDevice) DeactivateApplication

func (d *EdgeDevice) DeactivateApplication(appUUID uuid.UUID, waitUntilDeactivated bool,
	timeout time.Duration)

DeactivateApplication deactivates the specified application instance.

func (*EdgeDevice) DeleteFile

func (d *EdgeDevice) DeleteFile(fileName string)

DeleteFile removes a file from the device.

func (*EdgeDevice) DeletePartition

func (d *EdgeDevice) DeletePartition(partitionLabel string)

DeletePartition removes the named GPT partition (e.g. "P3", EVE's /persist) from the device's boot disk, leaving the space it occupied unallocated.

Distinct from DestroyPartitionFilesystem, which keeps the partition and only ruins its contents: this removes the partition itself, so EVE does not find one at all and looks elsewhere.

The device must be powered off (PowerOff), and the test must have declared RequireCapabilities{CAPABILITY_EDIT_DEVICE_DISK}.

func (*EdgeDevice) DeleteScratchImage

func (d *EdgeDevice) DeleteScratchImage(name string)

DeleteScratchImage removes an image created by CreateScratchImage.

func (*EdgeDevice) DestroyPartitionFilesystem

func (d *EdgeDevice) DestroyPartitionFilesystem(partitionLabel string)

DestroyPartitionFilesystem makes the filesystem on the named GPT partition (e.g. "P3", EVE's /persist) unrecoverable, so that EVE reformats it on the next boot. The partition table is left intact, which is the state a real filesystem loss leaves behind.

The device must be powered off (PowerOff), and the test must have declared RequireCapabilities{CAPABILITY_EDIT_DEVICE_DISK}.

func (*EdgeDevice) DetachUSBHub

func (d *EdgeDevice) DetachUSBHub(id string)

DetachUSBHub unplugs a hub attached by AttachUSBHub. Everything plugged into it has to be detached first: QEMU would take it down together with the hub, but the block nodes and scratch images of drives would stay behind.

func (*EdgeDevice) DetachUSBStorage

func (d *EdgeDevice) DetachUSBStorage(id string)

DetachUSBStorage unplugs a drive attached by AttachUSBStorage and deletes its image. device_del completes asynchronously, so releasing the block node is retried for a short while.

func (*EdgeDevice) DialViaSSH

func (d *EdgeDevice) DialViaSSH(network, address string) (net.Conn, error)

DialViaSSH opens a TCP connection to address, tunneled through an SSH connection to this device (an SSH direct-tcpip channel), as if address is dialed from the device itself. Useful for reaching services that only listen on the device's own loopback interface, e.g. the Kubevirt VNC proxy gated by ApplicationInstanceConfig.RemoteConsole.

func (*EdgeDevice) DisableRebootAccounting

func (d *EdgeDevice) DisableRebootAccounting(reason string)

DisableRebootAccounting stops the teardown check that a device's observed reboot count matches the expected one, recording why.

A hard power-cycle is what this exists for. Following one the device sometimes republishes the reboot time the harness counts from and sometimes does not (see PowerOn), so the same test lands on different counts from run to run and no fixed declaration can be right. Use it only where the count is genuinely unknowable; a test whose reboots are merely more numerous than the harness assumes should declare them with ExpectAdditionalReboots instead.

func (*EdgeDevice) ExecuteQMP

func (d *EdgeDevice) ExecuteQMP(execute string, arguments any) json.RawMessage

ExecuteQMP runs one QMP command on the hypervisor running the device and returns the command's "return" member ("{}" for a command that returns nothing). arguments is marshaled into the command's "arguments" object: a map, a struct, or nil for a command without arguments. The test fails if the command is refused or cannot be delivered; see TryExecuteQMP for callers that expect refusals.

Power control belongs to PowerOff, PowerOn and HardReboot, which keep the broker's view of the device in step; QMP commands such as quit or system_powerdown bypass that.

Requires RequireCapabilities{CAPABILITY_QMP}.

func (*EdgeDevice) ExpectAdditionalReboots

func (d *EdgeDevice) ExpectAdditionalReboots(count int)

ExpectAdditionalReboots declares that the device will reboot count more times on its own, as a required consequence of a configuration change rather than at the test's request.

Additional, because it adds to the running total rather than stating what the total should be: the framework's own reboot-driven flows have already contributed to it.

Close audits observed reboots against expected ones, so a reboot EVE performs by design is reported as a failure unless it is declared. The reboot-driven flows the framework itself drives (RequestReboot, SoftReboot, HardReboot, UpgradeEVE) declare their own; this is for flows where the trigger is a config change and the reboot is EVE's chosen means of carrying it out — converting a cluster member back to a single node, for instance, which reboots to restore the pre-cluster /var/lib before k3s starts.

Declare it before applying the config that causes it: the audit only compares totals, but a declaration that races the observation reads as an accident in the log.

A cross-flavor boot-disk conversion is the other case: it runs an offline shrink/grow across more reboots than UpgradeEVE accounts for, and how many is known only to the test driving it, since a plain kvm<->k conversion with no resize reboots differently than a shrink+grow.

func (*EdgeDevice) FileExists

func (d *EdgeDevice) FileExists(fileName string) bool

FileExists checks whether a file exists on the device.

func (*EdgeDevice) GetAppDNSLogs

func (d *EdgeDevice) GetAppDNSLogs(
	appUUID uuid.UUID, match DNSLogMatch) []*eveflowlog.DnsRequest

GetAppDNSLogs returns DNS request logs for the specified application matching the provided criteria.

func (*EdgeDevice) GetAppFlowLogs

func (d *EdgeDevice) GetAppFlowLogs(
	appUUID uuid.UUID, match FlowLogMatch) []*eveflowlog.FlowRecord

GetAppFlowLogs returns flow records for the specified application matching the provided criteria.

func (*EdgeDevice) GetAppInfo

func (d *EdgeDevice) GetAppInfo(appUUID uuid.UUID) *eveinfo.ZInfoApp

GetAppInfo returns the last recorded runtime information for the specified application, or nil if no info message for that app has been received yet.

func (*EdgeDevice) GetAppLogs

func (d *EdgeDevice) GetAppLogs(appUUID uuid.UUID, match LogMsgMatch) []LogMsg

GetAppLogs returns application log messages matching the provided criteria.

func (*EdgeDevice) GetAppMetadata

func (d *EdgeDevice) GetAppMetadata(appUUID uuid.UUID) *eveinfo.ZInfoAppInstMetaData

GetAppMetadata returns the last recorded metadata associated with the specified application instance, or nil if none has been received yet.

func (*EdgeDevice) GetAppMetrics

func (d *EdgeDevice) GetAppMetrics(appUUID uuid.UUID) *evemetrics.AppMetric

GetAppMetrics returns the last recorded metrics for the specified application, or nil if no metrics message for that app has been received yet.

func (*EdgeDevice) GetArch

func (d *EdgeDevice) GetArch() string

GetArch returns the CPU architecture of the device ("amd64" or "arm64"), as determined during Setup (see TestHarness.selectArch) -- not merely the preferred architecture requested via EVETEST_PREFERRED_ARCH, which can differ from the device's actual one on a broker that does not support it (selectArch falls back to whatever the broker does support).

func (*EdgeDevice) GetBlobInfo

func (d *EdgeDevice) GetBlobInfo() *eveinfo.ZInfoBlobList

GetBlobInfo returns the last recorded information about stored blobs on the device, or nil if no blob info message has been received yet.

func (*EdgeDevice) GetClusterInfo

func (d *EdgeDevice) GetClusterInfo() (*eveinfo.ZInfoKubeCluster, time.Time)

GetClusterInfo returns the last recorded information about the Kubernetes cluster and the time the device stamped it, so that snapshots from different devices can be ordered. Returns nil and a zero time if no such info message has been received yet.

The snapshot may be arbitrarily old: only the elected leader publishes cluster info, so a device that has lost the election (or has been powered off) keeps reporting whatever it last sent, indefinitely.

func (*EdgeDevice) GetClusterMetrics

func (d *EdgeDevice) GetClusterMetrics() *evemetrics.KubeClusterMetrics

GetClusterMetrics returns the last recorded metrics for the Kubernetes cluster, or nil if no cluster metrics message has been received yet.

func (*EdgeDevice) GetClusterUpdateInfo

func (d *EdgeDevice) GetClusterUpdateInfo() *eveinfo.ZInfoKubeClusterUpdateStatus

GetClusterUpdateInfo returns the last recorded information regarding the Kubernetes cluster update, or nil if no such info message has been received yet.

func (*EdgeDevice) GetConfig

func (d *EdgeDevice) GetConfig() *EdgeDeviceConfig

GetConfig returns the current device configuration.

func (*EdgeDevice) GetContentTreeInfo

func (d *EdgeDevice) GetContentTreeInfo(ctUUID uuid.UUID) *eveinfo.ZInfoContentTree

GetContentTreeInfo returns the last recorded information about the specified content tree, or nil if no info message for it has been received yet.

func (*EdgeDevice) GetDeviceIPAddress

func (d *EdgeDevice) GetDeviceIPAddress(netAdapterLogicalLabel string) []net.IP

GetDeviceIPAddress returns IP addresses assigned to the specified network adapter. If netAdapterLogicalLabel is empty, IP addresses from all adapters are returned.

func (*EdgeDevice) GetDeviceInfo

func (d *EdgeDevice) GetDeviceInfo() *eveinfo.ZInfoDevice

GetDeviceInfo returns the last recorded device information, or nil if no info message has been received yet.

func (*EdgeDevice) GetDeviceMetrics

func (d *EdgeDevice) GetDeviceMetrics() *evemetrics.DeviceMetric

GetDeviceMetrics returns the last recorded device-level metrics, or nil if no metrics message has been received yet.

func (*EdgeDevice) GetHardwareInfo

func (d *EdgeDevice) GetHardwareInfo() *eveinfo.ZInfoHardware

GetHardwareInfo returns the last recorded hardware inventory information, or nil if no hardware info message has been received yet.

func (*EdgeDevice) GetLocationInfo

func (d *EdgeDevice) GetLocationInfo() *eveinfo.ZInfoLocation

GetLocationInfo returns the last recorded device location information, or nil if no location info message has been received yet.

func (*EdgeDevice) GetLogs

func (d *EdgeDevice) GetLogs(match LogMsgMatch) []LogMsg

GetLogs returns device log messages matching the provided criteria.

func (*EdgeDevice) GetNTPSources

func (d *EdgeDevice) GetNTPSources() *eveinfo.ZInfoNTPSources

GetNTPSources returns the last recorded NTP sources configured on the device, or nil if no NTP sources info message has been received yet.

func (*EdgeDevice) GetNetworkInstanceInfo

func (d *EdgeDevice) GetNetworkInstanceInfo(niUUID uuid.UUID) *eveinfo.ZInfoNetworkInstance

GetNetworkInstanceInfo returns the last recorded information about the specified network instance, or nil if no info message for it has been received yet.

func (*EdgeDevice) GetNetworkInstanceMetrics

func (d *EdgeDevice) GetNetworkInstanceMetrics(
	niUUID uuid.UUID) *evemetrics.ZMetricNetworkInstance

GetNetworkInstanceMetrics returns the last recorded metrics for the specified network instance, or nil if no metrics message for it has been received yet.

func (*EdgeDevice) GetState

func (d *EdgeDevice) GetState() api.EVEDeviceState

GetState returns the current lifecycle state of the device.

func (*EdgeDevice) GetVolumeInfo

func (d *EdgeDevice) GetVolumeInfo(volumeUUID uuid.UUID) *eveinfo.ZInfoVolume

GetVolumeInfo returns the last recorded information about the specified storage volume, or nil if no info message for it has been received yet.

func (*EdgeDevice) GetVolumeMetrics

func (d *EdgeDevice) GetVolumeMetrics(volumeUUID uuid.UUID) *evemetrics.ZMetricVolume

GetVolumeMetrics returns the last recorded metrics for the specified storage volume, or nil if no metrics message for it has been received yet.

func (*EdgeDevice) GrowDisk

func (d *EdgeDevice) GrowDisk(newSizeBytes uint64)

GrowDisk enlarges the device's boot disk to newSizeBytes, leaving the added space unallocated past the last partition rather than extending any partition into it.

That free tail is the precondition for EVE's in-field repartition to grow into slack instead of shrinking /persist to make room, and it cannot be arranged at device creation: EVE's own image generator sizes the partitions to fill whatever disk it is given.

The device must be powered off (PowerOff), and the test must have declared RequireCapabilities{CAPABILITY_EDIT_DEVICE_DISK}.

func (*EdgeDevice) HardReboot

func (d *EdgeDevice) HardReboot(waitUntilRebooted bool)

HardReboot triggers device reboot through the broker.

func (*EdgeDevice) KubectlListNames

func (d *EdgeDevice) KubectlListNames(resource string) ([]string, error)

KubectlListNames returns the name of every object of one resource type in the EVE app namespace. An error means the device was unreachable, k3s was down, or the output did not parse. A caller in Eventually must retry each of these.

func (*EdgeDevice) ListAppVMIRS

func (d *EdgeDevice) ListAppVMIRS(
	appUUID uuid.UUID, timeout time.Duration) ([]string, error)

ListAppVMIRS returns the sorted names of every VMIRS belonging to appUUID, whichever purge generation each one is. A VMIRS name embeds the app's purge counter (base.GetAppKubeNameWithPurge), which makes this the one vantage point from which a stale generation surviving a purge is observable at all: pillar's own DomainStatus is keyed by app UUID and so can only ever describe one of them.

Attribution is by the appDomainNameLabel selector rather than the name, since the label value also carries a version and an appnum that do not matter here. An empty list with a nil error means the app has no VMIRS; an error means the list could not be read, which a caller inside an Eventually should retry rather than fail on.

func (*EdgeDevice) ListDirEntries

func (d *EdgeDevice) ListDirEntries(dir string) ([]string, error)

ListDirEntries returns the sorted names of the entries in a directory on the device.

A directory that does not exist yields an empty list and no error: EVE creates many of its directories lazily, when a particular hypervisor, storage backend or agent first needs one, so "not there yet" is a normal observation rather than a failure. An error means the listing itself could not be run.

func (*EdgeDevice) ListUSBDevices

func (d *EdgeDevice) ListUSBDevices() (USBDeviceList, error)

ListUSBDevices returns the USB devices EVE sees, read from /sys/bus/usb/devices over SSH. It returns an error instead of failing the test, so it can be polled from Eventually while a device is still enumerating.

func (*EdgeDevice) ListUSBDevicesInsideApp

func (d *EdgeDevice) ListUSBDevicesInsideApp(appUUID uuid.UUID,
	auth AuthMethod) (USBDeviceList, error)

ListUSBDevicesInsideApp is ListUSBDevices for the guest of an application: the USB devices its kernel enumerates, read over SSH into the application (see RunShellScriptInsideApp for how it is reached). A device passed through from EVE shows up here with the ids and serial number it has on EVE, but on the guest's own bus and port.

func (*EdgeDevice) Name

func (d *EdgeDevice) Name() string

Name returns the device name.

func (*EdgeDevice) PathExists

func (d *EdgeDevice) PathExists(target string) (bool, error)

PathExists checks whether a path exists on the device, whatever kind of node it is - a directory, a symlink or a device node, not just a regular file.

Two differences from FileExists, which is otherwise equivalent: this accepts any node type, and it reports a failure of the check itself as an error instead of failing the test. That makes it usable from inside Eventually, where a device may be rebooting or still converging and the caller wants to retry rather than fail.

func (*EdgeDevice) PowerOff

func (d *EdgeDevice) PowerOff()

PowerOff hard-powers off the device through the broker (bypassing any graceful ACPI shutdown). The broker RPC blocks until the provider confirms the VM is stopped, so no separate wait parameter is needed.

func (*EdgeDevice) PowerOn

func (d *EdgeDevice) PowerOn(waitUntilOnline bool)

PowerOn powers the device back on through the broker and optionally waits until it boots and reports back to the controller.

TODO: following a true hard power-off, nodeagent does not reliably republish an updated ZInfoDevice.LastRebootTime (the reboot-reason detection that rebootAndWait's wait relies on appears to assume a cooperative in-place OS reboot, not an external power-cycle). Until that's root-caused on the EVE side, pass waitUntilOnline=false here and confirm recovery some other way (e.g. via WaitUntilAppIsRunning on the relevant apps).

func (*EdgeDevice) PrepareShutdown

func (d *EdgeDevice) PrepareShutdown()

PrepareShutdown signals a graceful shutdown via EdgeDevConfig.Shutdown: pillar deactivates every app instance on the device in response. Unlike Reboot/PowerOff, this has no power effect of its own -- the device stays up, which is why callers pair it with a separate power-off step.

func (*EdgeDevice) PurgeApplication

func (d *EdgeDevice) PurgeApplication(appUUID uuid.UUID, volumeGen VolumeGenerationPolicy,
	waitUntilPurged bool, timeout time.Duration)

PurgeApplication purges the specified application instance and its state. See VolumeGenerationPolicy for volumeGen.

func (*EdgeDevice) ReachableAddress

func (d *EdgeDevice) ReachableAddress(port uint16) (string, error)

ReachableAddress returns the host:port through which the given TCP port of the device can be reached from the test process, for handing an endpoint of the device, such as its SSH port, to a tool the test runs.

func (*EdgeDevice) ReadFile

func (d *EdgeDevice) ReadFile(fileName string) ([]byte, error)

ReadFile reads the contents of a file from the device.

func (*EdgeDevice) RebootApplication

func (d *EdgeDevice) RebootApplication(appUUID uuid.UUID, waitUntilRebooted bool,
	timeout time.Duration)

RebootApplication requests a reboot of the specified application instance.

func (*EdgeDevice) RequestReboot

func (d *EdgeDevice) RequestReboot(waitUntilFetched bool, waitUntilRebooted bool)

RequestReboot requests a device reboot via configuration.

If waitUntilFetched is true, the function blocks until the device has fetched the config carrying the incremented reboot counter, so the caller knows the request reached the device before, e.g., cutting its connectivity. If waitUntilRebooted is true, it additionally waits until the reboot completes; see rebootAndWait.

func (*EdgeDevice) RequestRefusedEVEUpgrade

func (d *EdgeDevice) RequestRefusedEVEUpgrade(targetEVEVersion string,
	targetEVEHypervisor Hypervisor, datastoreType BaseOSDatastoreType)

RequestRefusedEVEUpgrade applies a base-OS update that EVE is expected to reject outright, and returns as soon as the configuration is applied.

A rejection is not a failed upgrade. EVE refuses in place -- before it writes a partition and before it reboots -- so there is nothing to wait for and no reboot to account for, which is what separates this from UpgradeEVE's expectRevert: that one waits out two reboots for an image that was installed and then rolled back. What the rejection looks like is the caller's to assert: the reported error, and that the device is still running what it was.

func (*EdgeDevice) RunKubectl

func (d *EdgeDevice) RunKubectl(args string, timeout time.Duration) (string, error)

RunKubectl runs kubectl on the device with the given arguments and returns its trimmed standard output. The call goes through "eve exec kube" because kubectl exists only in the kube container, so the device has to be an EVE-K node.

The returned error carries kubectl's own standard error, which says why the command was refused. A caller polling a cluster that is still coming up should treat any error as a retryable "not yet": kubectl fails this way while k3s is starting, and that is indistinguishable here from a real refusal.

func (*EdgeDevice) RunShellScript

func (d *EdgeDevice) RunShellScript(script string, timeout time.Duration,
	stdoutWatchdogTimeout time.Duration) (stdout, stderr string, err error)

RunShellScript executes the provided shell script on the device over SSH and returns its standard output and standard error as strings.

If timeout is non-zero, execution is bounded by the given duration and will be canceled if the timeout expires. If timeout is zero, no explicit deadline is applied.

If stdoutWatchdogTimeout is non-zero, the script will be terminated if it produces no output on stdout for longer than the specified duration. This acts as a "watchdog" to detect stalled scripts.

func (*EdgeDevice) RunShellScriptInsideApp

func (d *EdgeDevice) RunShellScriptInsideApp(appUUID uuid.UUID, auth AuthMethod,
	script string, timeout time.Duration,
	stdoutWatchdogTimeout time.Duration) (stdout, stderr string, err error)

RunShellScriptInsideApp executes a shell script inside an application instance over SSH and returns its standard output and standard error.

The method discovers SSH endpoints for the application by inspecting:

  1. Port-forwarding ACL rules (port 22 mapped through a local network instance) -- the device IP on the uplink adapter plus the external port.
  2. Switch network instance interfaces -- the app IP at port 22 (directly bridged, reachable on the SDN network).
  3. RoutesTowardsEve entries in the SDN network model -- if any SDN network's router has a route towards the EVE device that covers a VIF's IP, that IP:22 is tried. This makes air-gap NI apps reachable once the app acting as their gateway has IP forwarding enabled.

auth specifies how to authenticate with the application's SSH server (username/password or client certificate). timeout and stdoutWatchdogTimeout behave the same as in RunShellScript.

func (*EdgeDevice) SoftReboot

func (d *EdgeDevice) SoftReboot(waitUntilRebooted bool)

SoftReboot reboots the device from the console/SSH.

func (*EdgeDevice) SyncDisks

func (d *EdgeDevice) SyncDisks()

SyncDisks flushes the device's filesystem caches. Call this before PowerOff: test devices mount /persist without dirsync (Setup adds set_no_dirsync), so a hard power-off can otherwise leave an app's image layers as zero-length files.

func (*EdgeDevice) TryExecuteQMP

func (d *EdgeDevice) TryExecuteQMP(execute string, arguments any) (json.RawMessage, error)

TryExecuteQMP is ExecuteQMP returning the error instead of failing the test, for commands whose refusal is an expected outcome, e.g. blockdev-del while the node is still in use by a device being unplugged.

func (*EdgeDevice) UnexpectedRebootCount

func (d *EdgeDevice) UnexpectedRebootCount() int

UnexpectedRebootCount is how many times the device has been seen rebooting beyond what the test asked for (RequestReboot, SoftReboot, HardReboot, UpgradeEVE, ExpectAdditionalReboots): the same count Close audits at the end of every test, exposed so that a test waiting for something a crash would prevent can fail at once, naming the cause, instead of waiting out its timeout. A reboot is recognized from the boot time the device reports in its info messages, so it registers once the device is back up and talking to the controller, not at the moment it goes down.

func (*EdgeDevice) UpgradeEVE

func (d *EdgeDevice) UpgradeEVE(targetEVEVersion string, targetEVEHypervisor Hypervisor,
	datastoreType BaseOSDatastoreType, waitUntilUpgraded bool, expectRevert bool,
	timeout ...time.Duration)

UpgradeEVE upgrades the EVE OS to the specified version and optionally waits until the upgrade completes or reverts. datastoreType selects how the target rootfs is delivered to the device -- see BaseOSDatastoreType. When expectRevert is true, the upgrade is expected to fail and EVE to revert to the previous version -- the function then waits for the target version to show a FAILED status instead of waiting for it to become active. A reverted upgrade causes two reboots (one to try the new version, one to revert), so the expected reboot count is incremented accordingly.

An optional timeout bounds the wait instead of the default, which is sized for an ordinary base-OS upgrade: download, one reboot, done. A cross-flavor boot-disk conversion is a different animal -- it also runs an offline shrink+grow across several reboots and then brings up a whole container-cluster stack -- and it lands close enough to the default that a healthy conversion and an expired budget are separated by a couple of minutes of host load. A test driving one should pass a longer timeout, otherwise it reports a conversion that was still progressing as a failure.

func (*EdgeDevice) WaitForClusterNodeIsReady

func (d *EdgeDevice) WaitForClusterNodeIsReady(timeout time.Duration)

WaitForClusterNodeIsReady waits until this device's own ZInfoKubeCluster report shows this device as a Ready node with healthy cluster storage.

Only the elected leader node publishes cluster info, so this is only meaningful when called on a single-node cluster (where the sole device is necessarily the leader) or on a device already known to be the leader; for a multi-node cluster where the leader isn't known in advance, use EdgeCluster.WaitUntilNodesAreReady instead.

It accepts the last report already received, which after a reboot can predate it. See EdgeCluster.WaitForFreshNodesReady. A report for another cluster, such as one a reused device sent during the previous test, is never accepted (see clusterReportMatches).

func (*EdgeDevice) WaitForUSBDevice

func (d *EdgeDevice) WaitForUSBDevice(t gomega.Gomega, serial string,
	timeout, polling time.Duration) *USBDeviceInfo

WaitForUSBDevice polls ListUSBDevices until a device with the serial number is enumerated and returns it, failing the test through t if timeout passes first. A listing error counts as not enumerated yet and is retried.

func (*EdgeDevice) WaitUntilAppIsRunning

func (d *EdgeDevice) WaitUntilAppIsRunning(
	appUUID uuid.UUID, timeoutExcludingDownload time.Duration)

WaitUntilAppIsRunning waits until the specified application reaches the running state or fails.

Safe to call even if the app already has RUNNING history from an earlier phase: it subscribes to live updates before checking the current snapshot, so a stale historical record can never be mistaken for a new transition.

timeoutExcludingDownload is the maximum time to wait excluding any period spent actively downloading (i.e. in DOWNLOAD_STARTED state with advancing progress). If a download stalls for downloadStalledTimeout the function fails immediately regardless of this timeout.

func (*EdgeDevice) WaitUntilHasControllerCert

func (d *EdgeDevice) WaitUntilHasControllerCert(
	certType evecerts.ZCertType, certPEM []byte, timeout time.Duration)

WaitUntilHasControllerCert blocks until the device publishes the given controller certificate of the given type.

The device side is the only place this can be observed: /certs is an unauthenticated pull carrying no device UUID, so the controller does not even record the request, and the device never acknowledges which certificates it accepted. The ControllerCert publication is ephemeral (/run), which makes the observation inherently fresh across a reboot.

Certificates are matched on the PEM rather than on the hash, because the hash is truncated on its way to the device (Adam serves only the first 16 bytes, flagged SHA256_16BYTES) and comparing lengths that differ by truncation is easy to get wrong. The PEM is compared trimmed, matching Adam's own normalization. The type is part of the match because a certificate is only ever meaningful for the role it was served under.

func (*EdgeDevice) WatchAppInfo

func (d *EdgeDevice) WatchAppInfo(
	appUUID uuid.UUID) (updates <-chan *eveinfo.ZInfoApp, stop func())

WatchAppInfo subscribes to info updates for the specified application and returns a buffered channel that receives each new ZInfoApp as it arrives. Call the returned close function to stop watching and close the channel.

func (*EdgeDevice) WatchAppMetadata

func (d *EdgeDevice) WatchAppMetadata(
	appUUID uuid.UUID) (updates <-chan *eveinfo.ZInfoAppInstMetaData, stop func())

WatchAppMetadata subscribes to metadata updates for the specified application instance and returns a buffered channel that receives each new ZInfoAppInstMetaData as it arrives. Call the returned close function to stop watching and close the channel.

func (*EdgeDevice) WatchAppMetrics

func (d *EdgeDevice) WatchAppMetrics(
	appUUID uuid.UUID) (updates <-chan *evemetrics.AppMetric, stop func())

WatchAppMetrics subscribes to metrics updates for the specified application and returns a buffered channel that receives each new AppMetric as it arrives. Call the returned close function to stop watching and close the channel.

func (*EdgeDevice) WatchBlobInfo

func (d *EdgeDevice) WatchBlobInfo() (updates <-chan *eveinfo.ZInfoBlobList, stop func())

WatchBlobInfo subscribes to blob info updates and returns a buffered channel that receives each new ZInfoBlobList as it arrives. Call the returned close function to stop watching and close the channel.

func (*EdgeDevice) WatchClusterInfo

func (d *EdgeDevice) WatchClusterInfo() (
	updates <-chan *eveinfo.ZInfoKubeCluster, stop func())

WatchClusterInfo subscribes to Kubernetes cluster info updates and returns a buffered channel that receives each new ZInfoKubeCluster as it arrives. Call the returned close function to stop watching and close the channel.

func (*EdgeDevice) WatchClusterMetrics

func (d *EdgeDevice) WatchClusterMetrics() (
	updates <-chan *evemetrics.KubeClusterMetrics, stop func())

WatchClusterMetrics subscribes to Kubernetes cluster metrics updates and returns a buffered channel that receives each new KubeClusterMetrics as it arrives. Call the returned close function to stop watching and close the channel.

func (*EdgeDevice) WatchClusterUpdateInfo

func (d *EdgeDevice) WatchClusterUpdateInfo() (
	updates <-chan *eveinfo.ZInfoKubeClusterUpdateStatus, stop func())

WatchClusterUpdateInfo subscribes to Kubernetes cluster-update info messages and returns a buffered channel that receives each new ZInfoKubeClusterUpdateStatus as it arrives. Call the returned close function to stop watching and close the channel.

func (*EdgeDevice) WatchContentTreeInfo

func (d *EdgeDevice) WatchContentTreeInfo(
	ctUUID uuid.UUID) (updates <-chan *eveinfo.ZInfoContentTree, stop func())

WatchContentTreeInfo subscribes to info updates for the specified content tree and returns a buffered channel that receives each new ZInfoContentTree as it arrives. Call the returned close function to stop watching and close the channel.

func (*EdgeDevice) WatchDeviceInfo

func (d *EdgeDevice) WatchDeviceInfo() (updates <-chan *eveinfo.ZInfoDevice, stop func())

WatchDeviceInfo subscribes to device info updates and returns a buffered channel that receives each new ZInfoDevice as it arrives. Call the returned close function to stop watching and close the channel.

func (*EdgeDevice) WatchDeviceMetrics

func (d *EdgeDevice) WatchDeviceMetrics() (
	updates <-chan *evemetrics.DeviceMetric, stop func())

WatchDeviceMetrics subscribes to device-level metrics updates and returns a buffered channel that receives each new DeviceMetric as it arrives. Call the returned close function to stop watching and close the channel.

func (*EdgeDevice) WatchForCrash

func (d *EdgeDevice) WatchForCrash() *CrashWatch

WatchForCrash starts a CrashWatch on the device. Call Stop when done.

func (*EdgeDevice) WatchHardwareInfo

func (d *EdgeDevice) WatchHardwareInfo() (
	updates <-chan *eveinfo.ZInfoHardware, stop func())

WatchHardwareInfo subscribes to hardware info updates and returns a buffered channel that receives each new ZInfoHardware as it arrives. Call the returned close function to stop watching and close the channel.

func (*EdgeDevice) WatchLocationInfo

func (d *EdgeDevice) WatchLocationInfo() (
	updates <-chan *eveinfo.ZInfoLocation, stop func())

WatchLocationInfo subscribes to location info updates and returns a buffered channel that receives each new ZInfoLocation as it arrives. Call the returned close function to stop watching and close the channel.

func (*EdgeDevice) WatchLogs

func (d *EdgeDevice) WatchLogs(match LogMsgMatch) (logs <-chan LogMsg, stop func())

WatchLogs subscribes to device log entries and returns a buffered channel that receives each new matching LogMsg as it arrives. Call the returned stop function to unsubscribe and close the channel.

func (*EdgeDevice) WatchNTPSources

func (d *EdgeDevice) WatchNTPSources() (
	updates <-chan *eveinfo.ZInfoNTPSources, stop func())

WatchNTPSources subscribes to NTP sources updates and returns a buffered channel that receives each new ZInfoNTPSources as it arrives. Call the returned close function to stop watching and close the channel.

func (*EdgeDevice) WatchNetworkInstanceInfo

func (d *EdgeDevice) WatchNetworkInstanceInfo(
	niUUID uuid.UUID) (updates <-chan *eveinfo.ZInfoNetworkInstance, stop func())

WatchNetworkInstanceInfo subscribes to info updates for the specified network instance and returns a buffered channel that receives each new ZInfoNetworkInstance as it arrives. Call the returned close function to stop watching and close the channel.

func (*EdgeDevice) WatchNetworkInstanceMetrics

func (d *EdgeDevice) WatchNetworkInstanceMetrics(
	niUUID uuid.UUID) (updates <-chan *evemetrics.ZMetricNetworkInstance, stop func())

WatchNetworkInstanceMetrics subscribes to metrics updates for the specified network instance and returns a buffered channel that receives each new ZMetricNetworkInstance as it arrives. Call the returned close function to stop watching and close the channel.

func (*EdgeDevice) WatchVolumeInfo

func (d *EdgeDevice) WatchVolumeInfo(volumeUUID uuid.UUID) (
	updates <-chan *eveinfo.ZInfoVolume, stop func())

WatchVolumeInfo subscribes to info updates for the specified storage volume and returns a buffered channel that receives each new ZInfoVolume as it arrives. Call the returned close function to stop watching and close the channel.

func (*EdgeDevice) WatchVolumeMetrics

func (d *EdgeDevice) WatchVolumeMetrics(
	volumeUUID uuid.UUID) (updates <-chan *evemetrics.ZMetricVolume, stop func())

WatchVolumeMetrics subscribes to metrics updates for the specified storage volume and returns a buffered channel that receives each new ZMetricVolume as it arrives. Call the returned close function to stop watching and close the channel.

func (*EdgeDevice) WriteFile

func (d *EdgeDevice) WriteFile(fileName string, content []byte)

WriteFile writes content to a file on the device.

type EdgeDeviceConfig

type EdgeDeviceConfig struct {
	*eveconfig.EdgeDevConfig
	// contains filtered or unexported fields
}

EdgeDeviceConfig allows to build EdgeDevConfig. It provides some helper functions, but it is also possible to access the EdgeDevConfig directly (to maybe create and test invalid device config).

func NewEdgeDeviceConfig

func NewEdgeDeviceConfig(devName string) *EdgeDeviceConfig

NewEdgeDeviceConfig constructs an EdgeDeviceConfig. When creating a configuration prior to Setup (e.g. for bootstrap), do not include secrets that require encryption in a CipherBlock (such as datastore credentials or application cloud-init metadata). These values must be added only after evetest.Setup completes and the device is onboarded, when cryptographic material for object-level encryption becomes available.

func (*EdgeDeviceConfig) AddApplication

func (dc *EdgeDeviceConfig) AddApplication(config ApplicationInstanceConfig) uuid.UUID

AddApplication adds a new application instance to the device configuration and returns its generated UUID.

func (*EdgeDeviceConfig) AddBlankVolume

func (dc *EdgeDeviceConfig) AddBlankVolume(
	displayName string, sizeBytes uint64, clearText bool) uuid.UUID

AddBlankVolume adds a standalone empty (VCOT_BLANK) volume of the requested size to the device configuration and returns its UUID.

A blank volume is created by volumemgr as an empty block device of the given size, with no content downloaded into it and no application reference required (volumemgr creates standalone volumes; see the VolumeConfig.HasNoAppReferences handling in volumemgr). On a device whose /persist is ZFS, a non-container, non-ISO volume is backed by a ZFS zvol whose "volsize" property equals sizeBytes (rounded up to the ZFS volblocksize). That volsize is the provisioned size volumemgr reports in AppDiskMetric.ProvisionedBytes, which zedagent surfaces to the controller as ZMetricVolume.TotalBytes and VolumeResources.MaxSizeBytes.

clearText selects whether the volume is created outside the vault. A clear-text volume can be created while the vault is still locked, which isolates a test from vault readiness; an encrypted one exercises the vault-dependent path instead. Tests that are not about encryption should say which they mean rather than relying on a default, since the choice changes what the volume creation actually exercises.

func (*EdgeDeviceConfig) AddBond

func (dc *EdgeDeviceConfig) AddBond(config BondConfig)

AddBond adds a bond (link aggregation) interface to the device configuration.

func (*EdgeDeviceConfig) AddContentTree

func (dc *EdgeDeviceConfig) AddContentTree(
	displayName string, image ApplicationImageStorage) uuid.UUID

AddContentTree adds a ContentTree that no Volume and no application refers to, and returns its UUID.

volumemgr downloads a ContentTree eagerly, so the image blobs land in the content-addressable store without a Volume, a PVC or a CDI import ever being created. A test whose subject is the blobs themselves -- that they survive an event, or that a later deployment reuses them instead of downloading again -- wants exactly that, since going through AddVolume would drag the storage pipeline into the result.

func (*EdgeDeviceConfig) AddNetwork

func (dc *EdgeDeviceConfig) AddNetwork(netConfig NetworkConfig) uuid.UUID

AddNetwork : add new network configuration. This can be then referenced from NetworkAdapterConfig.

func (*EdgeDeviceConfig) AddNetworkAdapter

func (dc *EdgeDeviceConfig) AddNetworkAdapter(config NetworkAdapterConfig)

AddNetworkAdapter adds a new physical network adapter to the device configuration.

func (*EdgeDeviceConfig) AddNetworkInstance

func (dc *EdgeDeviceConfig) AddNetworkInstance(config NetworkInstanceConfig) uuid.UUID

AddNetworkInstance adds a new network instance to the device configuration and returns its generated UUID.

func (*EdgeDeviceConfig) AddPhysicalIO

func (dc *EdgeDeviceConfig) AddPhysicalIO(config PhysicalIOConfig)

AddPhysicalIO adds a raw PhysicalIO (assignable I/O) device to the device configuration. Use it for non-network devices and for device-model inconsistencies that AddNetworkAdapter cannot express. A given logical label is owned by exactly one of AddNetworkAdapter or AddPhysicalIO.

func (*EdgeDeviceConfig) AddSCEPProfile

func (dc *EdgeDeviceConfig) AddSCEPProfile(profile SCEPProfile)

AddSCEPProfile adds a new SCEP profile into the device configuration.

func (*EdgeDeviceConfig) AddVLANSubinterface

func (dc *EdgeDeviceConfig) AddVLANSubinterface(config VLANSubinterfaceConfig)

AddVLANSubinterface adds a VLAN subinterface to the device configuration.

func (*EdgeDeviceConfig) AddVolume

func (dc *EdgeDeviceConfig) AddVolume(
	displayName string, image ApplicationImageStorage, sizeBytes uint64) uuid.UUID

AddVolume adds a standalone (app-unreferenced) VCOT_DOWNLOAD volume of the requested size, with content sourced from image, and returns its UUID.

Unlike AddBlankVolume, the volume's content is populated by a real download (e.g. from a docker registry, or evetest's built-in HTTP/SFTP image server -- see CreateBlankImageFile), so this exercises the download and (for non-raw formats) disk-format conversion path in volumemgr, rather than the empty zero-content path.

func (*EdgeDeviceConfig) Clone

func (dc *EdgeDeviceConfig) Clone() *EdgeDeviceConfig

Clone creates a deep copy of the EdgeDeviceConfig.

func (*EdgeDeviceConfig) DeleteApplication

func (dc *EdgeDeviceConfig) DeleteApplication(appUUID uuid.UUID)

DeleteApplication removes an application instance identified by its UUID and its root volume (the one created together with it from ApplicationInstanceConfig.Image/DiskBytes). Any volumes referenced through Mounts are left untouched -- their lifecycle is independent of the application (see MountConfig); remove them explicitly with DeleteVolume.

func (*EdgeDeviceConfig) DeleteBond

func (dc *EdgeDeviceConfig) DeleteBond(logicalLabel string)

DeleteBond removes a bond identified by its logical label from the device configuration.

func (*EdgeDeviceConfig) DeleteContentTree

func (dc *EdgeDeviceConfig) DeleteContentTree(contentTreeUUID uuid.UUID)

DeleteContentTree removes a ContentTree added by AddContentTree, and the Datastore it referenced, from the device configuration.

func (*EdgeDeviceConfig) DeleteNetwork

func (dc *EdgeDeviceConfig) DeleteNetwork(networkUUID uuid.UUID)

DeleteNetwork : remove previously added network configuration.

func (*EdgeDeviceConfig) DeleteNetworkAdapter

func (dc *EdgeDeviceConfig) DeleteNetworkAdapter(logicalLabel string)

DeleteNetworkAdapter removes a network adapter identified by its logical label from the device configuration.

func (*EdgeDeviceConfig) DeleteNetworkInstance

func (dc *EdgeDeviceConfig) DeleteNetworkInstance(niUUID uuid.UUID)

DeleteNetworkInstance removes a network instance identified by its UUID from the device configuration.

func (*EdgeDeviceConfig) DeletePhysicalIO

func (dc *EdgeDeviceConfig) DeletePhysicalIO(logicalLabel string)

DeletePhysicalIO removes an I/O device added with AddPhysicalIO, identified by its logical label, from the device configuration. Network adapters are removed with DeleteNetworkAdapter instead. The device must not be assigned to any application any more (ApplicationInstanceConfig.IOAdapters); delete or update the application first.

func (*EdgeDeviceConfig) DeleteSCEPProfile

func (dc *EdgeDeviceConfig) DeleteSCEPProfile(profileName string)

DeleteSCEPProfile removes SCEP profile from the device configuration.

func (*EdgeDeviceConfig) DeleteVLANSubinterface

func (dc *EdgeDeviceConfig) DeleteVLANSubinterface(logicalLabel string)

DeleteVLANSubinterface removes a VLAN subinterface identified by its logical label from the device configuration.

func (*EdgeDeviceConfig) DeleteVolume

func (dc *EdgeDeviceConfig) DeleteVolume(volumeUUID uuid.UUID)

DeleteVolume removes a standalone volume (created via AddVolume or AddBlankVolume) and its associated ContentTree/Datastore, if any, from the device configuration. The volume must not currently be referenced by any application; detach it first with DetachVolume.

func (*EdgeDeviceConfig) MakeBootstrapConfig

func (dc *EdgeDeviceConfig) MakeBootstrapConfig() *eveconfig.BootstrapConfig

MakeBootstrapConfig : wrap device configuration into BootstrapConfig, which is used to carry the initial device configuration and gets installed into the /config partition.

func (*EdgeDeviceConfig) SetBaseOS

func (dc *EdgeDeviceConfig) SetBaseOS(storage ApplicationImageStorage, shortVersion string)

SetBaseOS configures an EVE OS upgrade from the given image storage source. shortVersion is the full EVE short version string (e.g. "12.1.0-kvm-amd64") that EVE will report in ZInfoDevSW.ShortVersion after the upgrade. Calling this again on the same config replaces the previously set BaseOS entry.

func (*EdgeDeviceConfig) SetConfigProperties

func (dc *EdgeDeviceConfig) SetConfigProperties(configProps *pillartypes.ConfigItemValueMap)

SetConfigProperties : add configuration properties into the device configuration.

func (*EdgeDeviceConfig) SetDisksLayout

func (dc *EdgeDeviceConfig) SetDisksLayout(layout DisksLayout)

SetDisksLayout configures the device's ZFS/RAID disk-array layout for its extra (non-boot) virtio disks (see RequireEdgeDevice.ExtraDisks).

Both DiskLayoutRAID1 and DiskLayoutRAID10 are represented, at the EVE API level, as a top-level DisksConfig with ArrayType RAID0 (stripe) whose Children are the individual RAID1 mirrors -- a single mirror for RAID1, two mirrors for RAID10 -- exactly matching how eden itself builds this structure (pkg/device/disksLayout.go, GetDisksConfig).

func (*EdgeDeviceConfig) SetLPS

func (dc *EdgeDeviceConfig) SetLPS(config LPSConfig)

SetLPS configures the Local Profile Server (LPS) settings for the device.

func (*EdgeDeviceConfig) UpdateApplication

func (dc *EdgeDeviceConfig) UpdateApplication(
	appUUID uuid.UUID, newConfig ApplicationInstanceConfig)

UpdateApplication updates an existing application instance identified by its UUID.

func (*EdgeDeviceConfig) UpdateBond

func (dc *EdgeDeviceConfig) UpdateBond(config BondConfig)

UpdateBond updates an existing bond identified by its logical label.

func (*EdgeDeviceConfig) UpdateNetwork

func (dc *EdgeDeviceConfig) UpdateNetwork(networkUUID uuid.UUID, newConfig NetworkConfig)

UpdateNetwork : update already added network configuration.

func (*EdgeDeviceConfig) UpdateNetworkAdapter

func (dc *EdgeDeviceConfig) UpdateNetworkAdapter(config NetworkAdapterConfig)

UpdateNetworkAdapter updates an existing network adapter identified by its logical label.

func (*EdgeDeviceConfig) UpdateNetworkInstance

func (dc *EdgeDeviceConfig) UpdateNetworkInstance(
	niUUID uuid.UUID, newConfig NetworkInstanceConfig)

UpdateNetworkInstance updates an existing network instance identified by its UUID.

func (*EdgeDeviceConfig) UpdateSCEPProfile

func (dc *EdgeDeviceConfig) UpdateSCEPProfile(profile SCEPProfile)

UpdateSCEPProfile updates an existing SCEP profile.

func (*EdgeDeviceConfig) UpdateVLANSubinterface

func (dc *EdgeDeviceConfig) UpdateVLANSubinterface(config VLANSubinterfaceConfig)

UpdateVLANSubinterface updates an existing VLAN subinterface identified by its logical label.

type ExistingEdgeDeviceReusePolicy

type ExistingEdgeDeviceReusePolicy int

ExistingEdgeDeviceReusePolicy defines how to reuse an existing EdgeDevice that already satisfies test requirements. Only one strategy can be selected. This helps control whether to reuse as-is, reset, or recreate the edge device before test execution.

const (
	// UseAsIs : do nothing special, keep existing state.
	UseAsIs ExistingEdgeDeviceReusePolicy = iota
	// RebootEdgeDevice : just reboot edge device matching the requirements.
	RebootEdgeDevice
	// ResetDeviceConfig : reset the device configuration by clearing all
	// application-related settings while preserving the device network configuration.
	ResetDeviceConfig
	// ResetDeviceConfigAndReboot : combines ResetDeviceConfig with RebootEdgeDevice.
	ResetDeviceConfigAndReboot
	// ReonboardEdgeDevice forces re-onboarding of the device, even if it was previously
	// onboarded.
	// It removes the OnboardingStatus and edge device certificate, clears TPM, recreates
	// the device entry in the controller, resets device configuration (see ResetDeviceConfig)
	// and then reboots the device.
	ReonboardEdgeDevice
	// CreateFromScratchWithInstaller : re-create VM even if already exists using
	// EVE installer image.
	CreateFromScratchWithInstaller
	// CreateFromScratchWithLiveImage : re-create VM even if already exists using
	// EVE live image.
	CreateFromScratchWithLiveImage
	// CreateFromScratchWithNetworkBoot : re-create VM even if already exists,
	// with no boot disk image attached (only a blank target disk sized
	// MinDiskSizeInMiB). The device is expected to boot over the network
	// (see the network model's DHCP.netboot_server_ip), install EVE onto
	// the blank disk, and reboot into it.
	CreateFromScratchWithNetworkBoot
)

type Filesystem

type Filesystem int

Filesystem identifies the filesystem type required or detected on an EVE device.

const (
	// FilesystemUndefined indicates no specific filesystem is required or detected.
	FilesystemUndefined Filesystem = iota
	// FilesystemEXT4 represents the ext4 filesystem.
	FilesystemEXT4
	// FilesystemZFS represents the ZFS filesystem.
	FilesystemZFS
)

func GetFilesystemParameterValue

func GetFilesystemParameterValue() Filesystem

GetFilesystemParameterValue returns the value set for the Filesystem parameter.

func (*Filesystem) FromString

func (f *Filesystem) FromString(s string) error

FromString parses a filesystem name string and sets the Filesystem value.

func (Filesystem) String

func (f Filesystem) String() string

type FlowLogMatch

type FlowLogMatch struct {
	Flow              *eveflowlog.IpFlow // match every non-zero value from the 5-tuple
	Inbound           bool
	VirtualNetAdapter string // logical label
	NetworkInstance   uuid.UUID
	// NotBefore and NotAfter relates to FlowRecord.startTime
	NotBefore time.Time
	NotAfter  time.Time
}

FlowLogMatch defines filtering criteria for matching application flow logs.

type FromStringer

type FromStringer interface {
	FromString(string) error
}

FromStringer should be implemented by a test parameter if its type is not a basic Go type.

type HTTPStorage

type HTTPStorage struct {
	ImageFormat            eveconfig.Format
	ImageSHA256            string
	MaxDownloadBytes       uint64
	ImageRelativePath      string
	ServerAddress          string
	ServerPort             uint16 // if not defined, we assume port 80 (HTTP) / 443 (HTTPS)
	UseHTTPS               bool   // true = HTTPS, false = HTTP
	HTTPSTrustedCACertsPEM []string
	// OmitPort leaves the port out of the datastore FQDN (the scheme's default
	// port is then used). Required for a local datastore: EVE recognizes it by
	// the FQDN ending with ".local", which a ":port" suffix would hide.
	OmitPort bool
}

HTTPStorage defines the location of an application image stored on an HTTP datastore.

type Hypervisor

type Hypervisor int

Hypervisor identifies the hypervisor type required or detected on an EVE device.

const (
	// HypervisorUndefined indicates no specific hypervisor is required or detected.
	HypervisorUndefined Hypervisor = iota
	// HypervisorKVM represents the KVM hypervisor.
	HypervisorKVM
	// HypervisorXen represents the Xen hypervisor.
	HypervisorXen
	// HypervisorKubevirt represents the KubeVirt hypervisor.
	HypervisorKubevirt
)

func GetHypervisorParameterValue

func GetHypervisorParameterValue() Hypervisor

GetHypervisorParameterValue returns the value set for the Hypervisor parameter.

func (*Hypervisor) FromString

func (h *Hypervisor) FromString(s string) error

FromString parses a hypervisor name string and sets the Hypervisor value.

func (Hypervisor) String

func (h Hypervisor) String() string

type IOAdapterConfig

type IOAdapterConfig struct {
	LogicalLabel string
	Type         evecommon.PhyIoType
}

IOAdapterConfig represents an I/O device other than a network adapter directly assigned to an application, e.g. a GPU (PhyIoHDMI) or a USB controller (PhyIoUSBController). LogicalLabel names the PhysicalIO entry (see EdgeDeviceConfig.AddPhysicalIO) and Type must match its type.

type InitOption

type InitOption func(*initOptions)

InitOption customizes how Init sets up the test harness.

func WithControllerFaults

func WithControllerFaults() InitOption

WithControllerFaults makes Init run the controller behind the fault injecting proxy, for tests which call ArmControllerFault. It only supplies the default: EVETEST_CONTROLLER_FAULTS, when the user sets it, decides either way, so that the proxy can still be forced off to check that these tests skip, or forced on for every test.

func WithTags

func WithTags(tags ...Tag) InitOption

WithTags attaches tags to the test or suite whose Init call it is passed to. It has no effect at run time: list-tests reads the tags statically from the source, shows them, and hands the ones of suites to CI. Pass constants from this package directly, e.g.

evetest.Init(test, evetest.WithTags(evetest.TagHeavyTest))

type LPSConfig

type LPSConfig struct {
	GlobalProfile string
	Address       string // IP[:port] or hostname[:port]
	AuthToken     string
}

LPSConfig contains configuration for the Local Profile Server.

type LocalNetworkInstanceConfig

type LocalNetworkInstanceConfig struct {
	DisplayName              string
	Port                     string // logical label or shared label
	Subnet                   *net.IPNet
	DHCPRange                pillartypes.IPRange
	Gateway                  net.IP
	Domain                   string
	NTPServers               []string
	DNSServers               []net.IP
	StaticDNSEntries         []pillartypes.DNSNameToIP
	PropagateConnectedRoutes bool
	StaticRoutes             []pillartypes.IPRouteConfig
	EnableFlowlog            bool
	MTU                      uint16
	ForwardLLDP              bool
	ClusterWide              bool
}

LocalNetworkInstanceConfig represents Local (L3, NAT-ed) Network Instance.

type LogMsg

type LogMsg struct {
	Severity  string
	Source    string
	Filename  string
	Message   string
	Timestamp time.Time
}

LogMsg represents a single log message emitted by the device or an application.

type LogMsgMatch

type LogMsgMatch struct {
	Severity         string
	Source           string
	Filename         string
	MsgHasSubstring  string
	MsgMatchesRegexp regexp.Regexp
	NotBefore        time.Time
	NotAfter         time.Time
}

LogMsgMatch defines filtering criteria for matching log messages.

type ManualProxyConfig

type ManualProxyConfig struct {
	Proxies       []ProxyServer
	ProxyCertsPEM []string
	Exceptions    []string // IP address or hostname or wildcard domain (e.g. *.local)
}

ManualProxyConfig represents a manually specified proxy configuration.

type MountConfig

type MountConfig struct {
	// VolumeUUID is the UUID of an existing volume, as returned by AddVolume
	// or AddBlankVolume.
	VolumeUUID uuid.UUID
	// MountDir is the path where the volume is mounted inside the guest.
	// Empty means the volume is attached as a raw block device instead: no
	// guest-side mount is performed, and the disk shows up as a plain block
	// device (e.g. /dev/sdX for a VM, or a device node under
	// /dev/eve/volumes/by-id for a container) -- see VolumeRefConfig.MountDir
	// handling in pillar's zedmanager/domainmgr/containerd.
	MountDir string
	// ReadOnly marks the volume as read-only inside the guest. This sets
	// Volume.Readonly, a property of the volume itself (the EVE API has no
	// separate per-reference read-only flag) -- so mounting the same volume
	// read-write from one application and read-only from another is not
	// possible; the last MountConfig applied wins.
	ReadOnly bool
}

MountConfig attaches an existing volume to an application, in addition to its root disk. The volume must already exist in the device configuration (created via EdgeDeviceConfig.AddVolume or AddBlankVolume) -- AddApplication/UpdateApplication fail if VolumeUUID does not reference one. The volume's lifecycle is independent of any application that mounts it: deleting the application does not delete it (use DeleteVolume explicitly), and DeleteVolume itself fails while any application still mounts it.

type NetworkAdapterConfig

type NetworkAdapterConfig struct {
	LogicalLabel  string
	PhysicalLabel string
	InterfaceName string
	PCIAddress    string
	USBAddress    string
	WirelessType  evecommon.WirelessType
	Usage         evecommon.PhyIoMemberUsage
	PNAC          PNAC

	// AssignmentGroup overrides the PhysicalIO assignment group. Defaults to
	// LogicalLabel when empty.
	AssignmentGroup string
	// ParentAssignmentGroup sets the parent assignment group (empty if none).
	ParentAssignmentGroup string

	// AllowLocalModifications enables the Local Profile Server (LPS) to modify
	// the network configuration of this adapter.
	AllowLocalModifications bool

	// Parameters below should be left empty if:
	//   - Usage is PhyIoUsageDedicated or PhyIoUsageDisabled, or
	//   - the (ethernet) adapter is used in VLANs-only mode
	//   - the (ethernet) adapter is a member of a bond
	Cost         uint8
	NetworkUUID  uuid.UUID
	StaticIP     net.IP // use only in combination with StaticNetworkConfig
	SharedLabels []string
}

NetworkAdapterConfig represents configuration for NIC (ethernet or wireless)

type NetworkConfig

type NetworkConfig interface {
	// contains filtered or unexported methods
}

NetworkConfig defines a generic interface for converting a network config into its corresponding EVE protobuf representation.

type NetworkInstanceConfig

type NetworkInstanceConfig interface {
	// contains filtered or unexported methods
}

NetworkInstanceConfig defines a generic interface for network-instance configurations that can be converted into EVE protobuf representation.

type NetworkProtocol

type NetworkProtocol uint8

NetworkProtocol defined for ACL rules.

const (
	// NetworkProtocolAny indicates that an ACL rule matches any network protocol.
	NetworkProtocolAny NetworkProtocol = iota
	// NetworkProtocolICMP indicates that an ACL rule matches ICMP traffic.
	NetworkProtocolICMP
	// NetworkProtocolTCP indicates that an ACL rule matches TCP traffic.
	NetworkProtocolTCP
	// NetworkProtocolUDP indicates that an ACL rule matches UDP traffic.
	NetworkProtocolUDP
)

func (NetworkProtocol) String

func (p NetworkProtocol) String() string

String returns string representation of the network protocol.

type NoIPNetworkConfig

type NoIPNetworkConfig struct {
	MTU uint16
}

NoIPNetworkConfig represents configuration for an Ethernet interface without assigning an IP address (no static IP and no DHCP client). It is typically used for L2-only setups (e.g., bridge members) or for cluster networks, where IP addresses are assigned from the cluster configuration.

type PACFileProxyConfig

type PACFileProxyConfig struct {
	// PACScript is the JavaScript of the PAC file, i.e. its FindProxyForURL.
	PACScript     string
	ProxyCertsPEM []string
}

PACFileProxyConfig represents a proxy configuration given as a proxy auto-config (PAC) script that EVE evaluates for every URL it opens on the port. EVE receives the script the way a controller sends it, base64-encoded inside the network's proxy config.

type PCIDevice

type PCIDevice struct {
	VendorID uint16
	DeviceID uint16
}

PCIDevice identifies a PCI device by vendor and device ID.

type PNAC

type PNAC struct {
	Enable      bool
	EAPIdentity string
	EAPMethod   eveconfig.EAPMethod
	// Name of the certificate enrollment profile used for authentication
	// (for example, a SCEP profile). Applicable only if the selected EAP
	// method requires a client certificate (e.g., EAP-TLS).
	CertEnrollmentProfileName string
}

PNAC : configuration for Port-based Network Access Control.

type PhysicalIOConfig

type PhysicalIOConfig struct {
	LogicalLabel  string
	PhysicalLabel string
	Type          evecommon.PhyIoType
	// AssignmentGroup defaults to LogicalLabel when empty.
	AssignmentGroup       string
	ParentAssignmentGroup string
	Usage                 evecommon.PhyIoMemberUsage
	// Physical addresses; every non-empty field is written into Phyaddrs.
	PCIAddress    string
	USBAddress    string
	InterfaceName string
	Serial        string
}

PhysicalIOConfig represents a raw PhysicalIO (assignable I/O) device in the device model. Use it for non-network devices and for deliberately inconsistent device models that AddNetworkAdapter cannot express (a specific Ptype, a phantom device at a chosen PCI address, a custom assignment group, etc.). A given logical label is owned by exactly one of AddNetworkAdapter or AddPhysicalIO.

type PortFwdRule

type PortFwdRule struct {
	Protocol     NetworkProtocol
	EdgeNodePort uint16
	AppPort      uint16
	// AdapterLabel, when non-empty, restricts this port-forwarding rule to
	// ports that carry this shared label (generates an "adapter" ACE match).
	AdapterLabel string
}

PortFwdRule is a port forwarding rule.

type ProxyAutoDiscoveryConfig

type ProxyAutoDiscoveryConfig struct {
	ProxyCertsPEM []string
}

ProxyAutoDiscoveryConfig represents a proxy configuration that enables automatic discovery of network proxy settings using WPAD & PAC.

type ProxyConfig

type ProxyConfig interface {
	// contains filtered or unexported methods
}

ProxyConfig defines a generic interface for proxy configuration types that can be converted into EVE protobuf representation.

type ProxyServer

type ProxyServer struct {
	Proto   evecommon.ProxyProto
	Address string // IP or hostname
	Port    uint16
}

ProxyServer defines a single proxy server with protocol, address, and port.

type RequireCapabilities

type RequireCapabilities struct {
	Capabilities []api.Capability
}

RequireCapabilities : requirement for one or more optional, provider-specific capabilities (e.g. forwarding of link-local L2 protocols). A test declaring this requirement is skipped when the active device provider does not advertise all of the listed capabilities.

type RequireDirectRegistryPulls

type RequireDirectRegistryPulls struct{}

RequireDirectRegistryPulls : requirement to skip applying configured REGISTRY_MIRROR_* mirrors, everywhere evetest would otherwise use them (K3s/containerd config on kubevirt devices, app datastore FQDNs), so every image pull goes to the real registry instead of a mirror's own address.

type RequireEdgeDevice

type RequireEdgeDevice struct {
	// Logical name used to reference the device within the evetest framework.
	Name string

	// Zero values mean that the test does not care about the particular resource size.
	// None of these will be ever created with zero count - not even ethernet interfaces.
	MinCPUs          uint8  // Default will be 4.
	MinRAMInMiB      uint32 // Default will be 8 GiB (8192 MiB).
	MinDiskSizeInMiB uint32 // Default will be 64 GiB (65536 MiB).

	WithEVEVersion string
	// WithEVERepo overrides the repo WithEVEVersion is pulled from; empty
	// means EVETEST_EVE_REPO. For a device that must use a different repo
	// than the one under test (e.g. TestEVEUpgrade's pre-upgrade device).
	WithEVERepo    string
	WithHypervisor Hypervisor
	WithTPM        bool
	// Active host TPM PCR banks. Empty uses the provider default.
	// Requires WithTPM and provider support.
	WithTPMPCRBanks []api.TPMPCRBank

	WithFilesystem Filesystem
	// Mount the vault (or the entire persist partition when using ext4) with the
	// DIRSYNC flag, which makes all directory-entry updates synchronous.
	// This incurs significant I/O overhead in virtualized environments,
	// most noticeably during container image unpacking, so it is disabled by
	// default. Enable it only when the test explicitly exercises durability under
	// sudden failure conditions such as power loss or kernel crashes.
	WithDirSync bool

	// Configuration injected into the /config partition.
	WithSoftSerial              string
	WithGrubOptions             []string
	WithInjectedBootstrapConfig *EdgeDeviceConfig
	WithInjectedNetworkOverride *pillartypes.DevicePortConfig
	// framework automatically adds SSH key and enables console
	WithInjectedConfigProperties *pillartypes.ConfigItemValueMap

	// USB/PCI devices to passthrough into the edge device VM.
	WithUSBPassthrough []USBDevice // TODO
	WithPCIPassthrough []PCIDevice // TODO

	// ExtraDisks specifies additional blank disks (size in bytes each) to
	// attach to the device's VM, beyond its main boot disk (MinDiskSizeInMiB).
	// Each entry becomes one extra virtio-blk disk, in the given order (i.e.
	// the first extra disk is the device's second disk overall). Intended for
	// tests that configure EVE-level disk layout/RAID via
	// EdgeDeviceConfig.SetDisksConfig.
	ExtraDisks []uint64

	// What to do if EdgeDevice is already available (and still manageable)
	// from the previous test:
	DeviceReusePolicy ExistingEdgeDeviceReusePolicy
}

RequireEdgeDevice : requirement to deploy single EVE device.

type RequireIPv6OnlyRegistryMirrors

type RequireIPv6OnlyRegistryMirrors struct{}

RequireIPv6OnlyRegistryMirrors : requirement to only use IPv6 addresses of configured OCI registry mirrors when building app image datastore configs (for tests where the device itself is IPv6-only). A registry with no IPv6 mirror address configured is simply not mirrored for that app (falls back to pulling from the real, un-mirrored registry) rather than failing or skipping the test -- a missing mirror is only a performance optimization.

type RequireInternetConnectivity

type RequireInternetConnectivity struct {
	RequireIPv6 bool
}

RequireInternetConnectivity : requirement to provide IPv4/IPv6 Internet connectivity.

type RequireNetworkModel

type RequireNetworkModel struct {
	// NetworkModel.ControllerConfig is filled by the framework, do not set from the test.
	*api.NetworkModel
}

RequireNetworkModel : required Evetest-SDN network model.

type Requirement

type Requirement interface {
	// contains filtered or unexported methods
}

Requirement is a marker interface for all test requirements (e.g. device, hypervisor, filesystem).

type SCEPProfile

type SCEPProfile struct {
	Name string
	// Full SCEP server URL, including scheme, host, and path.
	// Example: https://ca.example.com/scep
	SCEPServerURL string
	// If true, SCEP requests are sent via the controller-provided SCEP proxy.
	// If false, the device connects directly to the SCEP server.
	UseControllerProxy bool
	ChallengePassword  string
	CACertsPEM         []string
	CSR                CSRProfile
}

SCEPProfile : SCEP (Simple Certificate Enrollment Protocol) configuration profile. Defines how a device enrolls for X.509 certificates using SCEP, including server connectivity, trust anchors, and CSR parameters.

type SFTPStorage

type SFTPStorage struct {
	ImageFormat       eveconfig.Format
	ImageSHA256       string
	MaxDownloadBytes  uint64
	ImageRelativePath string
	ServerAddress     string
	ServerPort        uint16 // if not defined, we assume port 22
	Username          string
	Password          string
}

SFTPStorage defines the location and access parameters for an application image stored on an SFTP datastore

type StaticNetworkConfig

type StaticNetworkConfig struct {
	NetworkType evecommon.NetworkType
	MTU         uint16
	Subnet      *net.IPNet
	Gateway     net.IP
	Domain      string
	NTPServers  []string
	DNSServers  []net.IP
	ProxyConfig ProxyConfig
}

StaticNetworkConfig represents a statically assigned IP configuration to an Ethernet interface.

type SwitchNetworkInstanceConfig

type SwitchNetworkInstanceConfig struct {
	DisplayName     string
	Port            string // logical label or shared label
	EnableFlowlog   bool
	STPConfig       pillartypes.STPConfig // only applied for Switch NI with multiple ports
	MTU             uint16                // used only for airgap switch NI
	ForwardLLDP     bool
	VlanAccessPorts []pillartypes.VlanAccessPort // VLAN access port assignments; empty = no VLAN filtering
	ClusterWide     bool
}

SwitchNetworkInstanceConfig represents Switch (L2, bridged) Network Instance.

type T

type T struct {
	*testing.T
	// contains filtered or unexported fields
}

T wraps testing.T and adds evetest-specific failure handling.

The wrapper augments test failures with:

  • automatic stacktrace logging
  • optional pause-on-failure support (controlled by EVETEST_PAUSE_ON_FAILURE)

T is returned by evetest.Init(*testing.T) and MUST be used instead of the original *testing.T. Calling failure methods on the original *testing.T bypasses this additional behavior.

func Init

func Init(t *testing.T, opts ...InitOption) *T

Init initializes the test harness and must be called exactly once per test. When used inside a test suite, Init may be called multiple times, once per test case, but only a single harness instance will be created.

Options only take effect for the Init call that creates the harness: a test that may run standalone passes the options it needs, and inside a suite the ones given to the suite's own Init apply.

func (*T) Error

func (t *T) Error(args ...interface{})

Error logs the provided arguments, marks the test as failed, and continues execution.

This is equivalent to Log + Fail, with additional stacktrace logging and optional pause-on-failure behavior.

func (*T) Errorf

func (t *T) Errorf(format string, args ...interface{})

Errorf formats the message, marks the test as failed, and continues execution.

This is equivalent to Logf + Fail, with additional stacktrace logging and optional pause-on-failure behavior.

func (*T) Fail

func (t *T) Fail()

Fail marks the test as failed but allows execution to continue.

This mirrors testing.T.Fail, with the addition of stacktrace logging and optional pause-on-failure behavior.

func (*T) FailNow

func (t *T) FailNow()

FailNow marks the test as failed and stops execution immediately.

Like testing.T.FailNow, this only stops the test in the goroutine from which it is called.

func (*T) Fatal

func (t *T) Fatal(args ...interface{})

Fatal logs the provided arguments, marks the test as failed, and stops execution immediately.

Like testing.T.Fatal, this only stops the test in the goroutine from which it is called.

func (*T) Fatalf

func (t *T) Fatalf(format string, args ...interface{})

Fatalf formats the message, marks the test as failed, and stops execution immediately.

In addition to testing.T.Fatalf behavior, this logs the current stacktrace and optionally pauses test execution if EVETEST_PAUSE_ON_FAILURE is enabled.

type Tag

type Tag string

Tag labels a test or a test suite for tools that schedule or filter them.

const (
	// TagHeavyTest marks a test or suite that needs a powerful host: one focused
	// on EVE-k, or one that takes more than about 90 minutes on a strong host.
	// CI schedules whole suites, so a suite with this tag is run only on the
	// strong runners, never on the weak ones.
	TagHeavyTest Tag = "heavy"
)

type TestCase

type TestCase struct {
	// Test is the test function to execute.
	Test func(t *testing.T)

	// Variants defines multiple configurations under which the test
	// should be executed. Each variant is run as a subtest.
	Variants []TestVariant
}

TestCase represents a single logical test along with optional variants. If no variants are provided, the test is executed once with default parameters.

type TestHarness

type TestHarness struct {
	api.UnimplementedEvetestServer
	// contains filtered or unexported fields
}

TestHarness is the central runtime state for executing tests and test suites. It owns the gRPC server lifecycle and tracks the currently executing test and optional test-suite context.

func (*TestHarness) CollectInfo

func (th *TestHarness) CollectInfo(
	ctx context.Context, req *api.EVEDeviceRequest) (*api.CollectInfoResponse, error)

CollectInfo retrieves debug information from a device (eve-info tarball).

func (*TestHarness) ConnectConsoleToEVE

func (th *TestHarness) ConnectConsoleToEVE(
	stream api.Evetest_ConnectConsoleToEVEServer) error

ConnectConsoleToEVE carries an interactive serial-console session over gRPC. The client sends an EVEDeviceRequest as the first message; the server opens a broker console stream, forwards ConsoleProperties, then relays raw bytes bidirectionally between the CLI and the broker.

func (*TestHarness) ConnectTunnelToEVE

func (th *TestHarness) ConnectTunnelToEVE(
	stream api.Evetest_ConnectTunnelToEVEServer) error

ConnectTunnelToEVE carries a single TCP connection to an EVE device port over gRPC. The client sends an EVETunnelConnect as the first message; the server dials EVE, replies with TunnelConnected, then relays raw bytes bidirectionally.

func (*TestHarness) ConnectTunnelToSDN

func (th *TestHarness) ConnectTunnelToSDN(
	stream api.Evetest_ConnectTunnelToSDNServer) error

ConnectTunnelToSDN carries a TCP connection to the SDN SSH port over gRPC. The client sends an SDNRequest as the first message; the server dials the SDN SSH endpoint, replies with TunnelConnected, then relays raw bytes bidirectionally.

func (*TestHarness) Continue

func (th *TestHarness) Continue(
	ctx context.Context, req *api.ContinueRequest) (*api.ContinueResponse, error)

Continue test execution until the end/failure or another checkpoint.

func (*TestHarness) Exit

func (th *TestHarness) Exit(
	ctx context.Context, req *api.ExitRequest) (*api.ExitResponse, error)

Exit the test early. If the test is paused at a checkpoint or on failure, it will be marked as skipped. Otherwise, the process is terminated with os.Exit(0).

func (*TestHarness) GetAppFlowLogs

func (th *TestHarness) GetAppFlowLogs(
	req *api.AppRequest, stream api.Evetest_GetAppFlowLogsServer) error

GetAppFlowLogs streams flow logs and DNS request logs for an application.

func (*TestHarness) GetAppInfo

func (th *TestHarness) GetAppInfo(
	req *api.AppRequest, stream api.Evetest_GetAppInfoServer) error

GetAppInfo streams application-specific info from a device (ZInfoApp).

func (*TestHarness) GetAppLogs

func (th *TestHarness) GetAppLogs(
	req *api.AppRequest, stream api.Evetest_GetAppLogsServer) error

GetAppLogs streams logs from an EVE-managed application.

func (*TestHarness) GetAppMetrics

func (th *TestHarness) GetAppMetrics(
	req *api.AppRequest, stream api.Evetest_GetAppMetricsServer) error

GetAppMetrics streams application-level metrics from a device (appMetric).

func (*TestHarness) GetClusterInfo

func (th *TestHarness) GetClusterInfo(
	req *api.ClusterRequest, stream api.Evetest_GetClusterInfoServer) error

GetClusterInfo streams summary information for the entire Kubernetes cluster from all devices.

func (*TestHarness) GetClusterMetrics

func (th *TestHarness) GetClusterMetrics(
	req *api.ClusterRequest, stream api.Evetest_GetClusterMetricsServer) error

GetClusterMetrics streams metrics for the Kubernetes cluster (KubeClusterMetrics) from all devices.

func (*TestHarness) GetClusterUpdateInfo

func (th *TestHarness) GetClusterUpdateInfo(
	req *api.ClusterRequest, stream api.Evetest_GetClusterUpdateInfoServer) error

GetClusterUpdateInfo streams information about ongoing cluster updates.

func (*TestHarness) GetEVEConfig

func (th *TestHarness) GetEVEConfig(
	ctx context.Context, req *api.EVEDeviceRequest) (*api.EVEConfigResponse, error)

GetEVEConfig fetches the current configuration submitted to the EVE device.

func (*TestHarness) GetEVEConsoleOutput

func (th *TestHarness) GetEVEConsoleOutput(
	ctx context.Context, req *api.EVEDeviceRequest) (*api.ConsoleOutputResponse, error)

GetEVEConsoleOutput returns the full console output from the EVE device.

func (*TestHarness) GetEVEInfo

GetEVEInfo streams real-time system info from EVE device (ZInfoDevice).

func (*TestHarness) GetEVELogs

GetEVELogs streams logs from EVE device (agent logs, kernel logs, etc).

func (*TestHarness) GetEVEMetrics

GetEVEMetrics streams real-time metrics from EVE device (deviceMetric).

func (*TestHarness) GetNIInfo

func (th *TestHarness) GetNIInfo(
	req *api.NIRequest, stream api.Evetest_GetNIInfoServer) error

GetNIInfo streams info (ZInfoNetworkInstance) about a network instance (NI).

func (*TestHarness) GetNIMetrics

func (th *TestHarness) GetNIMetrics(
	req *api.NIRequest, stream api.Evetest_GetNIMetricsServer) error

GetNIMetrics streams metrics (ZMetricNetworkInstance) for a network instance.

func (*TestHarness) GetSDNConfigGraph

func (th *TestHarness) GetSDNConfigGraph(
	ctx context.Context, req *api.SDNRequest) (*api.SDNConfigGraphResponse, error)

GetSDNConfigGraph returns the SDN configuration visualized as a Graphviz dot-formatted graph.

func (*TestHarness) GetSDNNetworkModel

func (th *TestHarness) GetSDNNetworkModel(
	ctx context.Context, req *api.SDNRequest) (*api.SDNGetNetworkModelResponse, error)

GetSDNNetworkModel returns the SDN's abstract model of the network.

func (*TestHarness) GetSDNStatus

func (th *TestHarness) GetSDNStatus(
	ctx context.Context, req *api.SDNRequest) (*api.SDNStatusResponse, error)

GetSDNStatus returns the overall SDN (network emulator) status.

func (*TestHarness) HardRebootEVEDevice

func (th *TestHarness) HardRebootEVEDevice(
	ctx context.Context, req *api.EVEDeviceRequest) (*api.EVERebootResponse, error)

HardRebootEVEDevice forces immediate power cycle of the EVE device.

func (*TestHarness) SoftRebootEVEDevice

func (th *TestHarness) SoftRebootEVEDevice(
	ctx context.Context, req *api.EVEDeviceRequest) (*api.EVERebootResponse, error)

SoftRebootEVEDevice request a clean OS-level reboot on the EVE device.

func (*TestHarness) Status

func (th *TestHarness) Status(
	ctx context.Context, req *api.StatusRequest) (*api.StatusResponse, error)

Status returns current test execution state and active devices.

func (*TestHarness) StreamSDNLogs

func (th *TestHarness) StreamSDNLogs(
	req *api.SDNRequest, stream api.Evetest_StreamSDNLogsServer) error

StreamSDNLogs streams logs from the SDN VM to the gRPC client. This method acts as a simple forwarder: it subscribes to the SDN log stream and relays all received log messages to the caller over the gRPC stream. If the SDN client is not initialized, an error is returned. The stream will terminate when either the SDN closes the stream (EOF) or the context is canceled.

type TestParameterDefinition

type TestParameterDefinition struct {
	// Key is the unique identifier of the parameter.
	Key string

	// DefaultValue is the value used if the parameter is not explicitly set.
	DefaultValue interface{}

	// Description provides human-readable metadata about the parameter.
	Description TestParameterDescription
}

TestParameterDefinition describes a parameter that a test or test-suite can accept. Parameters may have a default value, can be overridden by test-suites, or via environment variables.

func CPUsParameter

func CPUsParameter() TestParameterDefinition

CPUsParameter is a predefined TestParameterDefinition for the device vCPU count. A value of 0 means the framework default (4 vCPUs) is used, unless the test raises it to a floor of its own.

func DiskSizeMiBParameter

func DiskSizeMiBParameter() TestParameterDefinition

DiskSizeMiBParameter is a predefined TestParameterDefinition for the device disk size. A value of 0 means the framework default (65536 MiB) is used.

func EVEVersionParameter

func EVEVersionParameter() TestParameterDefinition

EVEVersionParameter is a predefined TestParameterDefinition for the EVE version.

func FilesystemParameter

func FilesystemParameter() TestParameterDefinition

FilesystemParameter is a predefined TestParameterDefinition for the Filesystem parameter.

func HypervisorParameter

func HypervisorParameter() TestParameterDefinition

HypervisorParameter is a predefined TestParameterDefinition for the Hypervisor parameter.

func RAMSizeMiBParameter

func RAMSizeMiBParameter() TestParameterDefinition

RAMSizeMiBParameter is a predefined TestParameterDefinition for the device RAM size. A value of 0 means the framework default (8192 MiB) is used, unless the test raises it to a floor of its own.

func TPMParameter

func TPMParameter() TestParameterDefinition

TPMParameter is a predefined TestParameterDefinition for the TPM parameter.

type TestParameterDescription

type TestParameterDescription struct {
	// Summary is a human-readable explanation of what the parameter controls.
	Summary string
	// Default is the default value expressed as a human-readable string
	// (e.g. "kvm", "false", "42").
	// IMPORTANT: this field must be set to a string literal so that
	// list-tests can extract it via static AST analysis. Do not use
	// computed expressions such as method calls or variable references.
	Default string
	// AllowedValues describes the set of accepted values in human-readable form.
	// For enum parameters the recommended format is "<val1>|<val2>|<val3>".
	// For numeric ranges you might write "<minval>-<maxval>".
	// Leave empty if unrestricted (any value of the given type is allowed).
	// Same constraint as Default: must be a string literal.
	AllowedValues string
}

TestParameterDescription provides metadata for a test parameter, used when listing available tests (see "make list-tests").

type TestParameterValue

type TestParameterValue struct {
	// Key is the identifier of the parameter.
	Key string

	// Value is the concrete value assigned to the parameter.
	Value interface{}
}

TestParameterValue represents a concrete value assigned to a test parameter, typically by a test-suite when running parameterized tests.

type TestVariant

type TestVariant struct {
	// Name is the name of the test variant.
	Name string

	// Parameters contains the concrete parameter values applied for this variant.
	Parameters []TestParameterValue
}

TestVariant represents a named variation of a test case with a specific set of parameter values. Variants are typically used to run the same test logic under different configurations.

type TransparentProxyConfig

type TransparentProxyConfig struct {
	ProxyCertsPEM []string
}

TransparentProxyConfig represents configuration for transparent proxying.

type USBDevice

type USBDevice struct {
	VendorID  uint16
	ProductID uint16
}

USBDevice identifies a USB device by vendor and product ID.

type USBDeviceInfo

type USBDeviceInfo struct {
	Bus       uint16 // kernel-assigned bus number, not stable across boots
	Port      string // port path below the root hub, e.g. "2" or "2.3"
	VendorID  uint16
	ProductID uint16
	Serial    string // empty when the device reports none
}

USBDeviceInfo is one USB device as EVE's kernel enumerates it under /sys/bus/usb/devices. (USBDevice, the vendor/product pair, is a requirement type; see requirements.go.)

func (USBDeviceInfo) String

func (d USBDeviceInfo) String() string

String formats the device as "<bus>-<port> <vendor>:<product>", the sysfs name followed by the ids as lsusb prints them, plus the serial if any.

type USBDeviceList

type USBDeviceList []USBDeviceInfo

USBDeviceList is what ListUSBDevices returns. It prints itself, so it can be the subject of matchers.SatisfyPredicate.

func (USBDeviceList) FindBySerial

func (l USBDeviceList) FindBySerial(serial string) *USBDeviceInfo

FindBySerial returns the device with the given serial number, or nil.

func (USBDeviceList) String

func (l USBDeviceList) String() string

type UsernamePasswordAuth

type UsernamePasswordAuth struct {
	Username string
	Password string
}

UsernamePasswordAuth represents username/password authentication.

type VLANSubinterfaceConfig

type VLANSubinterfaceConfig struct {
	LogicalLabel       string
	InterfaceName      string
	ParentLogicalLabel string
	VlanID             uint16
	Cost               uint8
	NetworkUUID        uuid.UUID
	StaticIP           net.IP // use only in combination with StaticNetworkConfig
	SharedLabels       []string
	Usage              evecommon.PhyIoMemberUsage
}

VLANSubinterfaceConfig represents a single VLAN sub-interface.

type VirtualNetworkAdapter

type VirtualNetworkAdapter struct {
	LogicalLabel        string
	NetworkInstanceUUID uuid.UUID
	StaticIP            net.IP
	MAC                 net.HardwareAddr
	AccessVLAN          uint16
	PortFwdRules        []PortFwdRule
	ACLAllowRules       []ACLAllowRule
	InterfaceOrder      *uint32
}

VirtualNetworkAdapter represents application virtual network adapters (e.g., virtio, e1000).

type VolumeGenerationPolicy

type VolumeGenerationPolicy int

VolumeGenerationPolicy controls how PurgeApplication treats the GenerationCount on the app's referenced volumes, modeling the two shapes a real controller's purge can take depending on the volume being purged.

const (
	// BumpVolumeGeneration increments GenerationCount on every volume the
	// app references: a purge that rebuilds the app's storage from scratch
	// alongside its domain. Per eve-api's storage.proto, "generationCount
	// indicates the mutated volume needs to be purged and built from
	// scratch."
	BumpVolumeGeneration VolumeGenerationPolicy = iota
	// ReplaceVolumeConfig gives every volume the app references a fresh
	// UUID (GenerationCount reset to 0) rather than bumping the counter on
	// the one EVE already has, cycling the domain the same as
	// BumpVolumeGeneration.
	ReplaceVolumeConfig
)

type WiFiNetworkConfig

type WiFiNetworkConfig struct {
	// WiFi with static IP config is rather uncommon
	DHCPNetworkConfig
	SSID      string
	KeyScheme evecommon.WiFiKeyScheme
	Identity  string
	// The password must be supplied in pre-hashed form, as generated by `wpa_passphrase`.
	// (`PBKDF2-HMAC-SHA1` hash derived from the plaintext password and the SSID).
	Password string
	Priority int32
}

WiFiNetworkConfig represents a Wi-Fi configuration.

Directories

Path Synopsis
cmd
list-tests command
list-tests scans the evetest test suite under ./tests and prints all available test functions grouped into two sections:
list-tests scans the evetest test suite under ./tests and prints all available test functions grouped into two sections:
grpcapi
go
revive:disable -- this file lives alongside protobuf-generated code in a directory named "go"
revive:disable -- this file lives alongside protobuf-generated code in a directory named "go"
Package instances implements a small on-disk registry of running evetest instances (containers), so that the evetest CLI can find an instance without the user having to know its automatically assigned API port.
Package instances implements a small on-disk registry of running evetest instances (containers), so that the evetest CLI can find an instance without the user having to know its automatically assigned API port.
Package orchestrator runs several evetest tests or suites as parallel evetest instances (containers), each on its own API port.
Package orchestrator runs several evetest tests or suites as parallel evetest instances (containers), each on its own API port.
tests
portscan
Package portscan scans EVE devices from the network side with nmap and checks that whatever they expose is HTTPS-only and refuses anonymous access.
Package portscan scans EVE devices from the network side with nmap and checks that whatever they expose is HTTPS-only and refuses anonymous access.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL
⌂ 换网址