Skip to content

evetest: cache EVE disk image templates and support locally built live images - #6260

Open
rucoder wants to merge 4 commits into
lf-edge:masterfrom
rucoder:rucoder/evetest-image-templates-pr
Open

evetest: cache EVE disk image templates and support locally built live images#6260
rucoder wants to merge 4 commits into
lf-edge:masterfrom
rucoder:rucoder/evetest-image-templates-pr

Conversation

@rucoder

@rucoder rucoder commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Description

The evetest broker built one EVE disk image per device by running the EVE container image: roughly four minutes and ~2 GB of work each, serialized. A three-node cluster test paid that three times, every run.

This adds two related things.

An EVE image template cache. The broker now builds a configuration-independent template once per distinct (docker image content ID, disk size, installer flag, arch), caches it under $EVETEST_BROKER_IMAGE_DIR/templates/<key>/, and gives each device a qcow2 copy-on-write overlay backed by it. The device's own 5 MiB FAT config partition is assembled and written into the overlay's CONFIG partition afterwards, reproducing exactly what pkg/eve/runme.sh does with mcopy -o -i /bits/config.img -s /in/* ::/, so device configuration never enters the cache key. Templates are reference-counted so one backing a live VM is never deleted, guarded by an image-directory flock so two brokers sharing a directory cannot destroy each other's state, and evicted by age and disk pressure. Providers that have not been validated against it (qemu, proxmox) keep the existing per-device build path unchanged.

Support for a locally built EVE live image. During EVE development make live already produces the disk the broker spends four minutes recreating. EVETEST_EVE_LIVE_IMAGE=current points evetest at dist/<arch>/current/live.qcow2; the harness content-hashes it and uploads it — with installer/config.img and installer/firmware/* — only when the broker does not already hold that hash. The broker installs it as a template through the same machinery as above, so overlays, config injection, reference counting and eviction are shared with the container path rather than duplicated.

Measured on the same broker running TestSingleNodeCluster:

Container image path Local live image
Transfer 6.62 GB 2.1 GB in 19 s
Template creation 3 m 56 s (EVE container build) 9 s (unpack + sha256 verify)
Rerun, image unchanged cache hit cache hit, zero transfer

A locally built qcow2 cannot produce an installer flow, so a request combining make_installer with a live image is rejected rather than silently falling back to a container build — as is a provider that builds per device. Support is advertised as CAPABILITY_LOCAL_LIVE_IMAGE, so an outdated broker fails with a clear message instead of quietly testing a different EVE build.

Additional fix: NVRAM detection in libvirt teardown

The third commit fixes a separate, pre-existing bug in the libvirt provider's teardown that this work made impossible to ignore.

libvirt.go decided whether a domain had NVRAM with strings.Contains(xmlDesc, "<nvram>") — a bare tag. Libvirt emits the element with attributes (<nvram format='raw'>/…/OVMF_VARS.fd</nvram> on the test host), so the check never fired, DOMAIN_UNDEFINE_NVRAM was never passed, and every undefine failed with cannot undefine domain with nvram.

The domain was destroyed but its definition survived. Device names are deliberately stable within a suite so consecutive tests reuse the same VMs, so the next test failed to create its devices:

failed to define domain "eve-<id>-edge-dev2": domain 'ev-eve-<id>-edge-dev2'
already exists with uuid c5cc4dec-...

How to test and validate this PR

Container path (unchanged behaviour):

make evetest NAME=TestSingleNodeCluster

Expect Building EVE image template on the first device, Reusing cached EVE image template on subsequent devices with the same image, and one template directory under the broker's image dir.

Local live image path:

make live
EVETEST_EVE_LIVE_IMAGE=current make evetest NAME=TestSingleNodeCluster

Expect one upload and Installing EVE image template ... from an uploaded live image. Rerun the same command: expect zero uploads and Reusing cached EVE image template. Rebuild with make live and rerun: the new content hash produces exactly one new upload.

Both paths were validated end to end against a remote libvirt broker: device boots from the overlay and onboards into the Adam controller, which exercises the injected config partition (server address and onboard certificate) rather than merely proving the image boots.

Changelog notes

No user-facing changes. Test-framework only (evetest/); nothing under pkg/ or the EVE rootfs is touched.

PR Backports

  • 16.0-stable: No -- test-framework improvement, not a fix.
  • 14.5-stable: No -- test-framework improvement, not a fix.
  • 13.4-stable: No -- test-framework improvement, not a fix.

Checklist

  • I've provided a proper description

  • I've added the proper documentation

  • I've tested my PR on amd64 device

  • I've tested my PR on arm64 device

  • I've written the test verification instructions

  • I've set the proper labels to this PR

  • I've checked the boxes above, or I've provided a good reason why I didn't check them.

Not tested on arm64: the change is broker-side image plumbing with no architecture-specific code paths, and the validation broker is amd64.

@rucoder

rucoder commented Aug 1, 2026

Copy link
Copy Markdown
Contributor Author

@claude

@claude

claude Bot commented Aug 1, 2026

Copy link
Copy Markdown

Claude finished @rucoder's task in 5m 20s —— View job


Review complete

I focused on the broker-side image-template cache, GPT/config-partition handling, and the local-live-image upload path. Overall this is careful, well-documented code with the trust boundaries (tar path traversal, sha256 verification, hex/ref-name validation, GPT bounds/overflow checks) already handled. Two things worth a look, posted as inline comments:

  • Read the diff and new files
  • Review broker image template cache logic
  • Review GPT parsing / config injection
  • Review local live image upload path
  • Post inline review comments

Findings

  1. Template refcount is not lock-guarded against the cleanup goroutine (imagetemplate.go evict/addRef). addRef/removeRef/hasRefs/evict/candidates never take c.mutex, so a BuildImage handler acquiring a reference can interleave with the periodic evict between its hasRefs ReadDir and its RemoveAll, deleting a template's disk.qcow2 out from under a device that was just handed the overlay. Low probability (30-min sweep, test broker) and partly mitigated by evict's re-check, but it undermines the design's central "never deleted while referenced" guarantee.

  2. loadInstalled writes to disk under c.mutex on every lookup and is reached from the read-only hasTemplate predicate (imagetemplate.go:335). The LastUsed update serializes concurrent lookups behind a disk write and mutates meta.json as a side effect of a has… check. Minor.

Everything else — the single-flight build, tar staging/rename atomicity, config-partition injection mirroring runme.sh, capability gating of the live path, and non-owner housekeeping guards — looks correct. I did not review the generated *.pb.go files or the test files in depth.
· branch rucoder/evetest-image-templates-pr

diskHasher := sha256.New()
tr := tar.NewReader(f)
for {
hdr, err := tr.Next()
return nil
}
dir := c.templateDir(key)
if err := os.RemoveAll(dir); err != nil {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The refcount that guarantees "a template backing a live VM is never deleted" is not actually mutually excluded against reference acquisition. evict, hasRefs, candidates, addRef and removeRef never take c.mutex (only loadInstalled does), yet evict runs on the cleanup goroutine while addRef runs concurrently on BuildImage gRPC handlers.

So there is a TOCTOU window:

  1. ensureTemplate returns a cached ref (template currently unreferenced).
  2. cleanup calls evict(key)hasRefs reads an empty refs/ dir.
  3. makeDeviceImage calls addRef → writes the ref marker.
  4. evict proceeds to os.RemoveAll(dir), deleting the marker and disk.qcow2 that addRef just returned success for.

makeDeviceImage then builds the overlay backed by a now-deleted template disk → the device's VM has a missing backing file. The re-check in evict closes the common ordering but not the interleaving where the WriteFile lands between hasRefs's ReadDir and RemoveAll.

The window is tiny (cleanup fires every 30 min) and this is a test broker, so severity is low, but the strongest safety claim of the design rests on this being race-free. Consider serializing addRef/removeRef/hasRefs/evict under c.mutex.

Comment thread evetest/broker/imagetemplate.go Outdated
return nil, false
}
}
meta.LastUsed = time.Now()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

loadInstalled performs a synchronous file write (meta.save, to update LastUsed) on every cache lookup, and it runs while c.mutex is held, so a slow disk write serializes all concurrent template lookups. It is also reached from hasTemplate, which reads as a side-effect-free predicate (called in the BuildImage precondition) but in fact rewrites meta.json and mutates on-disk LastUsed. Minor for a handful of devices, but the write-under-lock and the hidden mutation in a has… check are both surprising; consider updating LastUsed outside the lock (or only from the acquire path, not the probe).

@rucoder

rucoder commented Aug 1, 2026

Copy link
Copy Markdown
Contributor Author

Thanks — both findings were real. Fixed in 0827bbd.

1. Refcount vs. the cleanup sweep. Confirmed, and worth separating from a window an earlier review had already looked at. That review closed the stale snapshot case by having evict re-check hasRefs, and accepted the gap between ensureTemplate returning and the first addRef landing on the grounds that it fails loudly — addRef errors and BuildImage fails, so no live VM is affected. The window you identified is different and genuinely open: an addRef landing between evict's hasRefs check and its RemoveAll deletes a template a device has already successfully referenced.

It is not theoretical. The regression test added here (TestEvictAddRefRace, 200 iterations of concurrent addRef/evict) fails 5 out of 5 runs against the pre-fix code, first failure typically within the first ten iterations.

The fix uses a dedicated refsMutex rather than the existing c.mutex, because hasRefs is reachable while c.mutex is held (ensureTemplateAttempt/hasTemplateloadInstalleddiscardUnusablehasRefs), so reusing it would self-deadlock — which is why these functions were unguarded in the first place. addRef, removeRef and evict's check-and-delete are now one critical section, with the lock ordering documented on the field: c.mutex may be held while acquiring refsMutex, never the reverse. candidates stays unguarded by design — it only builds a snapshot, and evict is now authoritative — with a comment saying so, since adding a lock there would create the cycle.

2. hasTemplate mutating state. Also fixed. loadInstalled gained a touch parameter: real cache hits still refresh LastUsed (age-based eviction depends on it), while the read-only hasTemplate predicate no longer performs a disk write under c.mutex.

go test -race -count=2 is clean, and mini-yetus reports no findings.

@rucoder
rucoder force-pushed the rucoder/evetest-image-templates-pr branch from 0827bbd to 9d5d0b4 Compare August 1, 2026 21:37
@codecov

codecov Bot commented Aug 1, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 24.48%. Comparing base (cc3374a) to head (852bfdc).
⚠️ Report is 5 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #6260      +/-   ##
==========================================
+ Coverage   24.13%   24.48%   +0.34%     
==========================================
  Files         512      522      +10     
  Lines       93537    95253    +1716     
==========================================
+ Hits        22575    23322     +747     
- Misses      69177    69961     +784     
- Partials     1785     1970     +185     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@rucoder

rucoder commented Aug 2, 2026

Copy link
Copy Markdown
Contributor Author

@claude i addressed your previouse comments. Review agin

@claude

claude Bot commented Aug 2, 2026

Copy link
Copy Markdown

Claude finished @rucoder's task in 1m 31s —— View job


Re-review complete

I re-reviewed the two fixes in response to your update.

  • Fix Achieve World Domination #1 — refcount vs. cleanup sweep. Confirmed fixed. addRef/removeRef and evict's check-and-delete now share a single refsMutex critical section (hasRefsLocked), so an addRef can no longer land between evict's reference check and its RemoveAll. The choice of a dedicated refsMutex over c.mutex is right — hasRefs is reachable via loadInstalledLocked → discardUnusable while c.mutex is held, so reusing c.mutex would self-deadlock. The lock-ordering rule (c.mutex may be held while acquiring refsMutex, never the reverse) is documented and holds across evict/candidates/discardUnusable; no cycle. candidates staying unguarded with evict as the authoritative re-check is sound. Nice regression test (TestEvictAddRefRace).

  • Fix Need to publish AssignableAdapters after a change #2hasTemplate mutating state. Confirmed fixed. The touch parameter cleanly separates the read-only predicate (touch=false, no meta.save under c.mutex) from genuine lookups that must refresh LastUsed for age-based eviction.

  • Scanned the rest of the diff — no new issues introduced by the fix commit.

One minor residual (inline): discardUnusable still does its hasRefs check and os.RemoveAll outside refsMutex — structurally the same check-then-delete window evict just closed. Much lower risk since it only fires on corrupt/incomplete templates, but folding it into one refsMutex section (like evict) would make the "never deleted while referenced" invariant uniform. Not a blocker.

The CodeQL "Zip Slip" flag on livetemplate.go is mitigated by the filepath.Clean + ../absolute-path rejection at lines 100–103.
· branch rucoder/evetest-image-templates-pr

Comment on lines +371 to +377
if c.hasRefs(key) {
log.Warnf("EVE image template %q is unusable (%s) but is still referenced by a "+
"working copy; leaving it in place", key, reason)
return
}
dir := c.templateDir(key)
if err := os.RemoveAll(dir); err != nil {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Residual (minor) of the same race the evict fix closed: discardUnusable does hasRefs (takes+releases refsMutex) and then os.RemoveAll(dir) outside refsMutex, so an addRef can still land between the check and the delete. Unlike evict, this is far lower risk — discardUnusable only fires on a template deemed corrupt/incomplete, which wouldn't normally be handed back to makeDeviceImage as a usable ref for a concurrent addRef — but the check-then-delete pattern here isn't guarded the way the design's "never deleted while referenced" invariant otherwise now is. For consistency, consider folding the hasRefs check + RemoveAll into one refsMutex critical section (hasRefsLocked), exactly like evict.

refsMutex, the touch param, and the documented lock ordering all look correct otherwise.

Comment thread evetest/broker/provider/libvirt.go Outdated
return fullCapabilitySet()
caps := fullCapabilitySet()
if p.DiskImageStrategy() != DiskImageLegacyBuild {
caps = append(caps, api.Capability_CAPABILITY_LOCAL_LIVE_IMAGE)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not think this belong to the provider.
Provider does not build the image, it is done in the provider-agnostic part of the broker.

And actually, does it even make sense to use this capability when libvirt or proxmox providers are used? They (usually) run on a remote machine, away from your eve repo. In my opinion, it makes sense to enable this only for the qemu mode (when everything including broker is inside the evetest container)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@milan-zededa yes, it does. this is exactly how i use libvirt broker today: I build local live image - it is faster than make eve , and then upload local image to broker

}

// DiskImageStrategy returns DiskImageLegacyBuild. Proxmox can never use
// DiskImageOverlay because uploadDiskImages ships the image to the PVE node,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For proxmox we could also just save the template as a file, then run uploadDiskImages only for config-overlayed disks.

After all, providers do not prepare the VM image. This is done by the provider-agnostic part of the broker. So when provider receives call to setup a new device, the qcow image is already prepared, including the config partition (and then proxmox provider calls uploadDiskImages).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@milan-zededa but what if we want to use native proxmox template feature? it cannot be provider agnostic

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So do we want to use the proxmox template feature? What is the added value as opposed to having one solution for all providers? Let's make that decision before expanding the provider API. I intentionally want to keep it minimal and avoid 3 different solutions for one problem if possible.

Comment thread evetest/broker/broker.go Outdated
})
} else {
// No EVE container image need exist on this broker at all on the live
// path -- that is the point of the feature -- so its content ID is

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove these AI-generated "-- that is the point of the feature --" comments (there are two instances at least)

Comment thread evetest/README.md Outdated
| `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 | `80` |
| `EVETEST_BROKER_TEMPLATE_RETENTION` | How long, in minutes, an unused EVE disk-image template (see [The EVE Image Template Cache](#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. Deliberately higher than `EVETEST_BROKER_DOCKER_DISK_USAGE_THRESHOLD`: broker hosts routinely idle above 80% (the reference host sits at 83% with 150 GB free), 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` |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove "(the reference host sits at 83% with 150 GB free)"

Comment thread evetest/README.md
| `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` | Test against a locally built live image instead of an EVE container image. Empty/unset uses the container path; the literal `current` resolves to `dist/<arch>/current/live.qcow2`; any other value is a path to a qcow2. See [Testing a Local EVE Build](#testing-a-local-eve-build) | -- |

@milan-zededa milan-zededa Aug 3, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this override EVETEST_EVE_VERSION?
What is the effect on for example TestEVEUpgrade. Does this override only the target image, while we continue the legacy image built for EVETEST_INITIAL_EVE_VERSION?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@milan-zededa good question. I'll double check

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For now I suggest to:

@rucoder

rucoder commented Aug 3, 2026

Copy link
Copy Markdown
Contributor Author

Good catch — I checked, and it is broken. Worse than just overriding EVETEST_EVE_VERSION.

setup.go establishes the precedence:

eveVersion := dev.requirement.WithEVEVersion        // per-device, explicit
if eveVersion == "" {
    eveVersion = viper.GetString(constants.EVEVersionEnv)
}

and my block then does eveVersion = localImg.Version unconditionally, so it stomps the per-device WithEVEVersion too — which is the strongest signal a test can give.

TestEVEUpgrade sets exactly that: RequireEdgeDevice{WithEVEVersion: initialVersion} (default 16.0.0-lts), while the target is applied separately via device.UpgradeEVE(targetVersion, …) and never goes through the device-image build path.

So with EVETEST_EVE_LIVE_IMAGE set, the effect is the opposite of what would be useful:

  • the initial device boots the local build instead of 16.0.0-lts, so the pre-upgrade premise is gone
  • the target is untouched, so the run becomes "upgrade from your local build to EVETEST_EVE_VERSION" — possibly a no-op or a downgrade, and definitely not what the test means
  • the reported version is the local dist/ directory name, so both ends look like the same build

To your questions directly: yes it overrides EVETEST_EVE_VERSION, and also the per-device override; and no, it does not override only the target — it overrides only the initial image, which is precisely backwards.

The minimum correct behaviour is that a local live image must not hijack a device that explicitly asked for a particular EVE version — such a device wants a specific released build and the local one is not it. I will make that device fall back to the container path rather than fail, so TestEVEUpgrade keeps working unchanged with the live image configured for the rest of the run.

Making the local build serve as the upgrade target is the genuinely useful version of this, and is what an image reference in ImageRef (rather than one global env var) would enable. I would rather do that as a follow-up than widen this PR.

I will fold this in with your other points.

@rucoder
rucoder force-pushed the rucoder/evetest-image-templates-pr branch 2 times, most recently from ca0df2e to 852bfdc Compare August 3, 2026 16:25
@github-actions
github-actions Bot requested a review from jsfakian August 3, 2026 21:11
@rucoder
rucoder force-pushed the rucoder/evetest-image-templates-pr branch from 687ed8d to 20e7177 Compare August 3, 2026 21:19

// DiskImageStrategy returns DiskImageLegacyBuild. The qemu provider attaches
// local files and could use DiskImageOverlay, but that has not been validated
// here yet.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be easy to validate, just run any test without broker IP defined.

qemu and proxmox are the priority for us. The qemu provider is used for running tests locally, while proxmox will be used for CI.

rucoder added 3 commits August 4, 2026 08:59
The broker built one EVE disk image per device by running the EVE container
image: roughly four minutes and ~2 GB of work each, serialized. A three-node
cluster test paid that three times, every run.

Build a configuration-independent template once per distinct (docker image
content ID, disk size, installer flag, arch), cache it under
templates/<key>/, and give each device a qcow2 copy-on-write overlay backed
by it. The device's own 5 MiB FAT config partition is assembled and written
into the overlay afterwards, reproducing what pkg/eve/runme.sh does with
mcopy onto /bits/config.img, so device configuration never enters the cache
key and a warm cache serves every device.

Templates are reference counted, so one backing a live VM is never deleted;
an image-directory flock makes a second broker sharing the directory
read-only for housekeeping; and unused templates are evicted by age and by
disk pressure. Providers that have not been validated against this keep the
per-device build path, selected by a new per-provider disk image strategy.

Signed-off-by: Mikhail Malyshev <mike.malyshev@gmail.com>
Teardown matched a bare "<nvram>" tag, but libvirt emits the element with
attributes -- format='raw' on this host -- so the check never fired,
DOMAIN_UNDEFINE_NVRAM was never passed, and every undefine failed with
"cannot undefine domain with nvram".

The domain was destroyed but its definition survived. Device names are stable
within a suite so that consecutive tests reuse the same VMs, so the next test
failed to create them with "already exists"; 32 leaked definitions had built up
on the shared broker before this was noticed.

Signed-off-by: Mikhail Malyshev <mike.malyshev@gmail.com>
(cherry picked from commit d5079ed)
Running a test required an EVE container image: ~6.6 GB pushed to the broker
and a ~4 minute container run there to produce a disk. But local EVE
development already produces that disk -- make live writes
dist/<arch>/current/live.qcow2 alongside installer/config.img and the OVMF
firmware, which is everything a template needs.

EVETEST_EVE_LIVE_IMAGE=current points evetest at that build. The harness
content-hashes the image and uploads it, with config.img and firmware, only
when the broker does not already hold that hash; the tar is streamed
ungzipped because qemu-img convert -c has already compressed the qcow2's
clusters. The broker installs it through the template cache's existing
builder parameter, so single-flight, atomic install, reference counting,
overlays, config injection and eviction are shared with the container path
rather than duplicated. Per-device disk sizing is applied by resizing the
overlay, leaving the shared backing file untouched.

Measured on one broker running TestSingleNodeCluster: 2.1 GB transferred in
19 s and a template ready in 9 s, against 6.62 GB and 3 m 56 s for the
container path; an unchanged image on a rerun transfers nothing.

Whether an uploaded live image can be consumed is the broker's determination,
not a device provider's -- a provider attaches disks and runs VMs, and never
builds or receives an image. The broker composes the advertised capability
set, deriving CAPABILITY_LOCAL_LIVE_IMAGE from the provider's disk image
strategy, so an outdated broker or an unsuitable provider fails clearly
instead of silently building from a different EVE image.

A device requesting a specific version via RequireEdgeDevice.WithEVEVersion
keeps taking the container path: it wants a particular released build, and
TestEVEUpgrade depends on that field to boot its pre-upgrade version while
applying the upgrade target through a separate path.

An installer image cannot be produced from a live qcow2, so that combination
is rejected rather than falling back.

Signed-off-by: Mikhail Malyshev <mike.malyshev@gmail.com>
The traversal guard checked the archive entry name; checking the joined
destination against the target directory instead rules out escape for certain
and is what static analysis can verify.

Signed-off-by: Mikhail Malyshev <mike.malyshev@gmail.com>
@rucoder
rucoder force-pushed the rucoder/evetest-image-templates-pr branch from 20e7177 to 65f7a64 Compare August 4, 2026 09:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants