Skip to content

test(e2e): migrate the blockdevice suite to the custom e2e-br image#2633

Open
Isteb4k wants to merge 39 commits into
mainfrom
e2e/virtual-disk-creation-custom-image
Open

test(e2e): migrate the blockdevice suite to the custom e2e-br image#2633
Isteb4k wants to merge 39 commits into
mainfrom
e2e/virtual-disk-creation-custom-image

Conversation

@Isteb4k

@Isteb4k Isteb4k commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Description

Move the whole blockdevice e2e suite from the ~350 MiB Alpine image to the
minimal from-scratch e2e-br image (~35 MiB), and fix what that surfaced:

  • All blockdevice data sources use the custom image (HTTP/Upload → qcow2 on
    Selectel, registry → ttl.sh container-disk, VI/CVI → precreated
    v12n-e2e-custom-bios); ISO tests use a custom EFI custom.iso.
  • Guest device lookup reads the SCSI serial from the sysfs VPD — the image runs
    no udev, so lsblk SERIAL / by-id are empty; SSH logs in as root, no sudo.
  • Resource waits go through observers, not Eventually (new sig-storage SIG
    label via SIGDescribe; new vmbda observer, vd BeResized, and a typed
    Project observer with BeDeployed replacing the generic UntilObjectState).
  • Controller fix: ProtectionService.RemoveProtection re-reads a fresh copy and
    patches finalizers under an optimistic lock with retry-on-conflict, so it no
    longer resurrects a finalizer on a terminating PVC (a 422 that errored and hung
    VM deletion). The two e2e log filters that masked this error are dropped.
  • Failure debug bundle for guest boot hangs: VNC screenshot capture now retries
    and leaves a *_screen_error.log breadcrumb on persistent failure, plus new
    serial-console capture (vm_<name>_serial.log) showing kernel/init output.

Two suites are gated off, both pending fixes outside this PR:

  • DataExports is skipped off-cluster (publish-mode export bug in the
    storage-volume-data-manager module — origin Ingress looked up in
    d8-user-authn instead of kube-system).
  • The entire VirtualDiskSnapshots suite is skipped: the vdsnapshot controller
    races the async freeze/unfreeze lifecycle (CanUnfreezeWithVirtualDiskSnapshot
    ignores Pending siblings and reads from a lagging cache, so late concurrent
    snapshots fail as "not consistent … has not been frozen"). TODO to un-skip
    once the controller is fixed.

VirtualDiskProvisioning no longer skips on NFS — VirtualImages on PVC now work
on filesystem storage classes, not only block.

New internal/object symbols are additive; other suites keep the Alpine image.

Why do we need it, and what problem does it solve?

e2e-br is ~10× smaller than the Alpine image the suite pulls today. This
migrates the blockdevice group to it and fixes the guest/observer/controller
issues that only appear on a truly minimal (no-udev, no-cloud-init) image.

What is the expected result?

The sig-storage (blockdevice) group passes on the custom image: disks
provision from every source (including on NFS), consumer VMs reach Running +
AgentReady, and resize works. DataExports runs on a cluster node / in CI;
VirtualDiskSnapshots is skipped pending the controller freeze/unfreeze fix.
Other suites are unaffected.

Checklist

  • The code is covered by unit tests.
  • e2e tests passed.
  • Documentation updated according to the changes.
  • Changes were tested in the Kubernetes cluster manually.

Changelog entries

section: test
type: chore
summary: Migrate the blockdevice e2e suite to the minimal custom e2e-br image.
impact_level: low

Point every VirtualDiskCreation data source (HTTP, Upload, registry,
VirtualImage, ClusterVirtualImage) at the minimal custom e2e-br image:
a Selectel qcow2 for HTTP/Upload and a ttl.sh container-disk for the
registry source. Drop the cloud-init provisioning for the test's VM —
it only needs the VM as a disk consumer with a live guest agent and
never logs in, so no user has to be created.

New object symbols (ImageURLCustomBIOS, ImageURLCustomContainer,
PrecreatedCVICustomBIOS) are additive; existing constants are left
untouched, so other e2e tests keep their behavior.

Signed-off-by: Dmitry Rakitin <dmitry.rakitin@flant.com>
@Isteb4k Isteb4k added this to the v1.10.0 milestone Jul 10, 2026
Isteb4k added 12 commits July 10, 2026 17:06
The custom e2e-br image is ~35 MiB and grows its root filesystem to the
disk on first boot, so the 400Mi disks are oversized. Use a 50Mi
vdCreationImageSize for the image-backed VirtualDisks instead.

Signed-off-by: Dmitry Rakitin <dmitry.rakitin@flant.com>
Switch every Alpine-based bootable-qcow2 source in the blockdevice suite
to the custom e2e-br image: HTTP (ImageURLAlpineBIOS -> ImageURLCustomBIOS),
registry (ImageURLContainerImage -> ImageURLCustomContainer) and the CVI
ObjectRef (PrecreatedCVIAlpineBIOS -> PrecreatedCVICustomBIOS).

Ubuntu-based tests (data_exports, virtual_disk_resizing, vd_snapshots
Ubuntu cases — they need systemd/SSH/cloud-init), the ISO/CD-ROM format
tests (ImageURLUbuntuISO / PrecreatedCVIUbuntuISO) and the non-bootable
testdata images are left untouched: the custom image is a bootable qcow2,
not a drop-in for those.

Signed-off-by: Dmitry Rakitin <dmitry.rakitin@flant.com>
The quota-exhausted test only needs a valid CVI source to trigger the QuotaExceeded path on PVC creation; use the custom e2e-br image instead of the non-bootable testdata qcow2.

Signed-off-by: Dmitry Rakitin <dmitry.rakitin@flant.com>
data_exports does not SSH — it waits for the guest agent and exports/restores data over the API. Use the custom e2e-br image (agent auto-starts) and drop the Ubuntu cloud-init provisioning.

Signed-off-by: Dmitry Rakitin <dmitry.rakitin@flant.com>
- add observer/vmbda package with BeAttached()/BeFailed() predicates;
- add vd observer BeResizing() predicate;
- source virtual_disk_resizing from the custom e2e-br image, log in as root
  (baked key) instead of the cloud user, and drop sudo (custom has neither
  cloud user nor sudo) via blockdevice-local SSH/lsblk helpers so the shared
  util helpers stay untouched;
- replace resource waits (UntilObjectPhase/UntilSSHReady/UntilDisksAreAttached)
  with observer + WaitFor + predicate; the only remaining Eventually is the
  guest-side lsblk-grows check, deliberately kept and commented as the sole
  sanctioned exception (no Kubernetes resource to observe).

Signed-off-by: Dmitry Rakitin <dmitry.rakitin@flant.com>
Add additive NewHTTPVDCustomBIOS/NewGeneratedHTTPVICustomBIOS constructors (the AlpineBIOS variants stay for other suites), switch importer_network_policy to them, and replace VI/VD phase waits with observer+WaitFor. The Project 'Deployed' wait is kept via UntilObjectState (Project has no typed client/Observer) with an explicit comment.

Signed-off-by: Dmitry Rakitin <dmitry.rakitin@flant.com>
Replace UntilObjectPhase/UntilVMAgentReady resource waits with observer+WaitFor (vi/vd/vm), source the HTTP case from the custom image (NewHTTPVDCustomBIOS), and disable cloud-init provisioning on the consumer VMs (custom image, agent-only).

Signed-off-by: Dmitry Rakitin <dmitry.rakitin@flant.com>
Source all disks from the custom e2e-br image (drop Ubuntu), disable cloud-init provisioning, and replace resource waits with observers: agent/disk/attachment via WaitFor, snapshot readiness via a vdsnapshot observer per snapshot, VM deletion via observer.WaitForDeleted, disks-attached via a VM WaitFor predicate. The transient filesystem-freeze check now uses a new vm.BeFilesystemFrozen() predicate observed from before the snapshot is created (replacing the Eventually poll).

Signed-off-by: Dmitry Rakitin <dmitry.rakitin@flant.com>
…vers

Add blockdevice-local root/no-sudo guest filesystem helpers (guest_fs.go) — the util.* originals stay cloud+sudo for other suites (vmop/restore). Convert data_exports resource waits to observers (agent/stopped/vmop-completed/disk-phase/snapshot-ready/delete), add vm.BeStopped(); the upload-endpoint retry stays as a commented Eventually exception (external HTTP, not a resource). Document the Project 'Deployed' wait exception in setupProject (Project has no typed Observer).

Signed-off-by: Dmitry Rakitin <dmitry.rakitin@flant.com>
Add ImageURLCustomISO/PrecreatedCVICustomISO (HTTP CVI for the custom EFI ISO). Point the ISO/CD-ROM boot and format-expectation tests at the custom ISO instead of the Ubuntu live-server ISO, and drop the VNC subiquity-installer screen assertion (Ubuntu-specific) — the CD-ROM boot test now only asserts the VM reaches Running with a bootable device (Never NoBootableDevice), which is what booting a custom ISO as CD-ROM verifies. Removes the RFB/VNC framebuffer machinery that check required.

Signed-off-by: Dmitry Rakitin <dmitry.rakitin@flant.com>
…tection

ProtectionService.RemoveProtection built its merge patch from a possibly stale
(informer cache) copy and resent the whole finalizers list. On a terminating
PersistentVolumeClaim this could re-add kubernetes.io/pvc-protection that the
built-in pvc-protection controller had already dropped, which the API server
rejects with 422 ("no new finalizers can be added if the object is being
deleted"). The VM deletion handler surfaced this as "failed to release PVC
protection" and never removed the VM cleanup finalizer, so VM deletion hung.

Re-read a fresh copy and patch it under an optimistic lock, retrying on
conflict, so only our finalizer is removed and no other finalizer is
resurrected.

Signed-off-by: Dmitry Rakitin <dmitry.rakitin@flant.com>
…rt skip

Blockdevice suite hardening on top of the custom e2e-br image:

- Introduce label.SIGDescribe and SIG labels (mirroring Kubernetes' [sig-*]
  ownership labels) and group the blockdevice specs under sig-storage, so the
  group can be run with `--label-filter='sig-storage'`.
- Resolve guest block devices by their SCSI VPD serial read straight from
  sysfs (/sys/block/*/device/vpd_pg80). The minimal e2e-br image runs no udev,
  so lsblk's SERIAL column and /dev/disk/by-id are empty; this also drops the
  awk one-liner whose single quotes clashed with d8 ssh -c '...'.
- Add vd observer predicate BeResized and wait on it after a resize instead of
  BeReady, which errors on the transient Resizing phase.
- Skip DataExports when the suite runs off-cluster: publish-mode export is
  currently broken by a bug in the storage-volume-data-manager module (origin
  Ingress looked up in d8-user-authn instead of kube-system). TODO to drop the
  skip once fixed.
- Filter the benign pvc-protection cleanup error and raise the VM-deletion wait
  to LongTimeout.

Signed-off-by: Dmitry Rakitin <dmitry.rakitin@flant.com>
@Isteb4k Isteb4k requested a review from yaroslavborbat as a code owner July 11, 2026 13:28
Isteb4k added 3 commits July 11, 2026 15:32
Signed-off-by: Dmitry Rakitin <dmitry.rakitin@flant.com>
Signed-off-by: Dmitry Rakitin <dmitry.rakitin@flant.com>
@Isteb4k Isteb4k changed the title test(e2e): source VirtualDiskCreation from the custom e2e-br image test(e2e): migrate the blockdevice suite to the custom e2e-br image Jul 11, 2026
@Isteb4k Isteb4k marked this pull request as draft July 11, 2026 13:47
Isteb4k added 9 commits July 13, 2026 11:05
- drop the redundant copyloopvar copy in the resize goroutine loop (unparam/copyloopvar)
- drop the always-constant bdKind param from getBlockDeviceLsblkSizeAsRoot
- download DataExports artifacts into a Ginkgo per-spec temp dir instead of the
  working directory, so a run leaves nothing untracked in the repo

Signed-off-by: Dmitry Rakitin <dmitry.rakitin@flant.com>
The disk is deleted right after being exported; its exporter pod keeps the PVC
mounted until the export tears down, so deletion can exceed the 1m MiddleTimeout
(it finishes in ~15s once released). Use LongTimeout.

Signed-off-by: Dmitry Rakitin <dmitry.rakitin@flant.com>
In-cluster measurement shows vd-data deletion after export is fast (~0-16s on
healthy storage). The earlier LongTimeout bump was unjustified: the >1m hang was
caused by transient LINSTOR degradation, and a larger timeout would have masked
that flaky storage rather than surfacing it.

Signed-off-by: Dmitry Rakitin <dmitry.rakitin@flant.com>
…sk in DataExports

The test deleted vd-data and blocked on observer.WaitForDeleted, which timed
out flakily. The wait is unnecessary: the restored disks use different names and
the VM's block devices are reassigned away from vd-data, so nothing depends on
the original being gone. vd-data is removed by the deferred cleanup at teardown.

Signed-off-by: Dmitry Rakitin <dmitry.rakitin@flant.com>
… in the no-consumer snapshot test

The no-consumer case deletes the throwaway VM to leave the disk without a
consumer, then snapshots it. It waited on observer.WaitForDeleted(VM), which
gated on VM-object teardown and timed out flakily. Wait instead for the actual
precondition — vdobs.BeDetached() (the disk has no attached VM) — which is what
the snapshot needs and does not depend on VM teardown latency.

Signed-off-by: Dmitry Rakitin <dmitry.rakitin@flant.com>
ProtectionService.RemoveProtection now re-reads a fresh copy and patches
under an optimistic lock with retry-on-conflict, so it no longer emits the
422 "no new finalizers can be added if the object is being deleted" error
when releasing the pvc-protection / vd-protection finalizers on a
terminating PersistentVolumeClaim. Drop the two log filters that masked it.

Signed-off-by: Dmitry Rakitin <dmitry.rakitin@flant.com>
…orts

Signed-off-by: Dmitry Rakitin <dmitry.rakitin@flant.com>
…c state helper

Project (deckhouse.io/v1alpha2) is not served by VirtClient, so tests waited
for it to reach "Deployed" via the generic unstructured util.UntilObjectState
helper. Add a Project-specialized observer that watches through the dynamic
client and decodes events into a typed Project (mirroring the storageprofile
observer), with a BeDeployed() predicate. Extend the hand-rolled Project type
with a partial Status (State) and switch the three call sites to the observer.
Remove the now-unused UntilObjectState helper.

Signed-off-by: Dmitry Rakitin <dmitry.rakitin@flant.com>
Signed-off-by: Dmitry Rakitin <dmitry.rakitin@flant.com>
@Isteb4k Isteb4k requested a review from hardcoretime July 14, 2026 13:24
Isteb4k added 4 commits July 14, 2026 15:57
The vdsnapshot controller can unfreeze the VM filesystem before all
concurrent RequiredConsistency snapshots are captured, so the late ones
fail with "virtual disk snapshot is not consistent ... has not been
frozen". The root cause is in CanUnfreezeWithVirtualDiskSnapshot (ignores
Pending siblings, reads them from a lagging cache), not in the test. Skip
with a TODO describing the controller bug until it is fixed.

Signed-off-by: Dmitry Rakitin <dmitry.rakitin@flant.com>
The suite is flaky beyond the concurrent case: single-disk/running-VM
cases also trip on the vdsnapshot controller's racy freeze/unfreeze
lifecycle (checkVMUnfrozen races the async thaw; CanUnfreeze can unfreeze
before all snapshots are captured). Skip the whole suite via a BeforeEach
Skip with a TODO covering both root causes until the controller is fixed.

Signed-off-by: Dmitry Rakitin <dmitry.rakitin@flant.com>
VirtualImages on PVC now work with filesystem storage classes, not only
block, so the NFS skip guard in the BeforeEach is obsolete. Drop it (and
the now-unused storage-class lookup) so the suite runs on NFS too.

Signed-off-by: Dmitry Rakitin <dmitry.rakitin@flant.com>
… hangs

When a guest boots but the agent never becomes ready, the VNC screenshot
is the key artifact — but capture failed silently, so a wedged guest left
no screenshot at all. Make screenshot capture retry and, on persistent
failure, write a *_screen_error.log breadcrumb explaining the miss.

Add serial console capture (vm_<name>_serial.log): the VNC frame only
shows the final screen, whereas the ttyS0 console shows the kernel/init
output that pins where boot wedged. Both run at the framework dump level,
so every failing blockdevice spec collects them before namespace cleanup.

Signed-off-by: Dmitry Rakitin <dmitry.rakitin@flant.com>
@Isteb4k Isteb4k marked this pull request as ready for review July 14, 2026 14:56
Isteb4k added 7 commits July 14, 2026 17:09
data_exports builds the VM via a bare vmbuilder.New, which leaves
Spec.Provisioning nil by default, so the explicit WithProvisioning(nil)
was a no-op. (The NewMinimalVM call sites keep it — there it clears the
AlpineCloudInit default.)

Signed-off-by: Dmitry Rakitin <dmitry.rakitin@flant.com>
Several blockdevice disks were built with no StorageClass, so they fell
back to the module default (the cluster-default SC) regardless of a
STORAGE_CLASS_NAME override. The expected-phase helpers derive their
answer from config.DefaultStorageClass, so on an Immediate override the
disk could land on the WaitForFirstConsumer cluster default and diverge
from what the test waits for.

This surfaced in DataExports: createUploadDisk built the restored upload
VDs with no SC, and waitDiskInExpectedPhase waited for Ready while the
disk parked at WaitForFirstConsumer, timing out after 5m.

Pin WithStorageClass(defaultStorageClass()) (or pass it to NewBlankVD /
WithPersistentVolumeClaim) on every disk so they use the SC the test
reasons about: data_exports (root/data/upload disks), virtual_disk_
provisioning, vd_snapshots and virtual_disk_resizing. Also unify the
vd_snapshots no-consumer disk onto defaultStorageClass() instead of the
ad-hoc ptr.To(cfg...Name).

Signed-off-by: Dmitry Rakitin <dmitry.rakitin@flant.com>
On WaitForFirstConsumer storage classes the Attached wait covers the whole
volume provisioning: the PVC binds only after the hotplug attachment pod
arrives, and a single csi-nfs CreateVolume under parallel suite load was
observed to take 84s — more than the whole MiddleTimeout. Raise only the
VMBDA BeAttached waits (and the post-resize recheck) to LongTimeout.

Signed-off-by: Dmitry Rakitin <dmitry.rakitin@flant.com>
On csi-nfs a VolumeSnapshot is a physical tar.gz copy of the volume taken
over internal NFS mounts, not an instant metadata operation: under parallel
suite load the mount setup alone was observed to take ~23s and the archive
copy ~31s, so ShortTimeout/MiddleTimeout waits for VirtualDiskSnapshot
readiness time out on an otherwise healthy path. Raise only these waits to
LongTimeout; disk-creation waits already use it.

Signed-off-by: Dmitry Rakitin <dmitry.rakitin@flant.com>
The BeReady predicate reported a bare "entered Failed phase", hiding the
reason the vdsnapshot controller left on the VirtualDiskSnapshotReady
condition (e.g. the latched VolumeSnapshot error). Include the condition
reason and message in the predicate error.

Signed-off-by: Dmitry Rakitin <dmitry.rakitin@flant.com>
The custom e2e-br image is 35Mi unpacked and the guest has no growpart, so
anything above ~50Mi is dead weight: importers, csi-nfs snapshots and
restores all stream the full logical size of disk.img, zeros included.
Root disks at 400Mi/450Mi/2Gi and blanks at 100Mi/51Mi/4Gi made every
import, snapshot and clone move ~8x more data than needed, which is what
kept tripping waits on NFS under parallel load.

Reuse the existing vdCreationImageSize constant (50Mi) for every root and
blank disk in the suite; disks that relied on auto-sizing get an explicit
size too. The resizing test grows disks by a relative +100Mi, so the
smaller base does not affect it.

Signed-off-by: Dmitry Rakitin <dmitry.rakitin@flant.com>
getImageInfo reads the first 10 MiB of the source to probe the format and
prepends that buffer to the remaining stream, so the combined reader always
starts at byte 0. getImageInfoStandard nevertheless wrote the header buffer
to the sampling temp file separately and then copied from the combined
reader, so the first 10 MiB ended up both duplicated in the sample and
counted twice in the byte-counted VirtualSize of raw sources.

As a result every VirtualImage created from a block-device source (a
VirtualDisk or VirtualDiskSnapshot) reported unpackedSize exactly 10 MiB
larger than the actual data, and disks cloned from such images failed size
validation ("the specified pvc size is insufficient") unless oversized.

Sample the temp file straight from the combined reader and count each byte
once. Covered by a unit test that pins VirtualSize for raw sources smaller
than the probed header, larger than it, and larger than the sampled window.

Signed-off-by: Dmitry Rakitin <dmitry.rakitin@flant.com>
@Isteb4k Isteb4k requested a review from diafour as a code owner July 14, 2026 22:20
Isteb4k added 2 commits July 15, 2026 00:29
Set both blockdevice size constants to 52Mi: on LINSTOR the actual block
device behind a 50Mi claim is ~52Mi (extent rounding minus DRBD metadata),
so 52Mi keeps the requested size aligned with what block storage actually
provisions for the smallest image-backed disks.

The resizing test now doubles the current disk size instead of adding a
fixed increment, so the growth stays proportional to whatever base size the
suite uses.

Signed-off-by: Dmitry Rakitin <dmitry.rakitin@flant.com>
Disks created from a VirtualImage or a VirtualDiskSnapshot no longer pin a
size: on block storage the actual device behind a claim is always larger
than requested (extent rounding, DRBD metadata), so an image or snapshot
taken from it never fits back into a disk of the same nominal size — any
hard-coded value breaks on some storage class. Let the controller derive
the size from the source instead.

Source disks provisioned from base images (CVI/HTTP/container-image/upload)
and blank disks keep an explicit size, back at 50Mi.

Signed-off-by: Dmitry Rakitin <dmitry.rakitin@flant.com>
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.

1 participant