mirror of
https://github.com/cozystack/cozystack.git
synced 2026-11-13 08:05:19 +09:00
Requesting immediate binding for every source, not just `upload`, is deliberate: a VMDisk is a standalone object populated before any VM consumes it, so on a WaitForFirstConsumer StorageClass a disk with no VM attached would never populate at all. What that costs was recorded nowhere an operator reads. On a node-pinned class the CDI worker pod chooses the node, so the volume binds where the importer landed rather than where the VM will run, and a VM constrained elsewhere -- a nodeSelector, a request only one node satisfies, or the Windows affinity from _cluster.scheduling.dedicatedNodesForWindowsVMs -- stays unschedulable against a node-affinity conflict. One disk is enough for that; it is not a multi-disk-only case. storageClass is immutable, so the exit is delete and recreate. The chart default `replicated` binds Immediate regardless and does not move. Also pinned in tests, in both directions the annotation is emitted: per source type (blank, image, disk) so a regression to the old upload-only condition reddens, and on the existing-DataVolume path, where `helm upgrade` retrofits the annotation onto disks created before it was unconditional -- inert for a volume that has already bound, and now a recorded decision rather than an unasserted side effect. Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com> Assisted-By: LLM
175 lines
6.5 KiB
YAML
175 lines
6.5 KiB
YAML
suite: vm-disk DataVolume source rendering on initial creation
|
|
|
|
# When no DataVolume exists yet, dv.yaml builds spec.source from .Values.source.
|
|
# This pins the refactor that routes every source type through the shared
|
|
# $desiredSource variable (used both for rendering and for the immutability
|
|
# comparison), so a regression there is caught without a live cluster.
|
|
release:
|
|
name: test-vm-disk
|
|
namespace: tenant-test
|
|
templates:
|
|
- templates/dv.yaml
|
|
tests:
|
|
- it: records an empty storage-class annotation when created with an empty storageClass
|
|
# Provenance is recorded even for "" (cluster default), so a later change to a
|
|
# concrete class is caught. Annotation-key presence (not its value)
|
|
# distinguishes a recorded empty class from a legacy disk with no annotation.
|
|
set:
|
|
storageClass: ""
|
|
asserts:
|
|
- equal:
|
|
path: metadata.annotations["vm-disk.cozystack.io/storage-class"]
|
|
value: ""
|
|
|
|
- it: treats an explicit null source as blank without crashing
|
|
# A direct HelmRelease consumer may write source: null; every .Values.source
|
|
# access must tolerate it (default dict) rather than crash hasKey on nil.
|
|
set:
|
|
source: null
|
|
asserts:
|
|
- notFailedTemplate: {}
|
|
- equal:
|
|
path: spec.source.blank
|
|
value: {}
|
|
|
|
- it: renders a blank source by default and records it in the source annotation
|
|
asserts:
|
|
- equal:
|
|
path: spec.source.blank
|
|
value: {}
|
|
- equal:
|
|
path: spec.storage.storageClassName
|
|
value: replicated
|
|
- equal:
|
|
path: metadata.annotations["vm-disk.cozystack.io/source"]
|
|
value: '{}'
|
|
# Immediate binding is requested for every source type, not only upload.
|
|
# A standalone VMDisk has no consumer to wait for, so on a
|
|
# WaitForFirstConsumer class the volume would otherwise never populate;
|
|
# the placement trade-off that buys is in README.md. Pinned per source so
|
|
# a regression to the old upload-only condition reddens here.
|
|
- equal:
|
|
path: metadata.annotations["cdi.kubevirt.io/storage.bind.immediate.requested"]
|
|
value: "true"
|
|
|
|
- it: renders an http source from values and records it in the source annotation
|
|
set:
|
|
source:
|
|
http:
|
|
url: https://example.com/img.qcow2
|
|
asserts:
|
|
- equal:
|
|
path: spec.source.http.url
|
|
value: https://example.com/img.qcow2
|
|
# The create-path annotation write is the linchpin of the immutability
|
|
# guard: later renders compare the operator's input against it, so a
|
|
# regression here would silently degrade every new disk to the lossy
|
|
# spec-compare fallback.
|
|
- equal:
|
|
path: metadata.annotations["vm-disk.cozystack.io/source"]
|
|
value: '{"http":{"url":"https://example.com/img.qcow2"}}'
|
|
# storageClass provenance is recorded at creation too, so a later clear-to-empty is caught.
|
|
- equal:
|
|
path: metadata.annotations["vm-disk.cozystack.io/storage-class"]
|
|
value: replicated
|
|
|
|
- it: records a query-string http URL with json-escaped special chars in the annotation
|
|
# Load-bearing round-trip: the source guard is a byte-for-byte string compare,
|
|
# so toJson's HTML-escaping of & < > (common in presigned S3 URLs) must be
|
|
# recorded verbatim and recompute identically. Locks the escaping so a future
|
|
# toJson change trips here instead of deadlocking every &-URL disk in prod.
|
|
set:
|
|
source:
|
|
http:
|
|
url: https://s3.example.com/x?a=1&b=2&c=<>
|
|
asserts:
|
|
- equal:
|
|
path: spec.source.http.url
|
|
value: https://s3.example.com/x?a=1&b=2&c=<>
|
|
- equal:
|
|
path: metadata.annotations["vm-disk.cozystack.io/source"]
|
|
value: '{"http":{"url":"https://s3.example.com/x?a=1\u0026b=2\u0026c=\u003c\u003e"}}'
|
|
|
|
- it: renders an image source as a cozy-public pvc clone
|
|
set:
|
|
source:
|
|
image:
|
|
name: ubuntu
|
|
asserts:
|
|
- equal:
|
|
path: spec.source.pvc.name
|
|
value: vm-default-images-ubuntu
|
|
- equal:
|
|
path: spec.source.pvc.namespace
|
|
value: cozy-public
|
|
- equal:
|
|
path: metadata.annotations["vm-disk.cozystack.io/source"]
|
|
value: '{"image":{"name":"ubuntu"}}'
|
|
# Immediate binding is requested for every source type, not only upload.
|
|
# A standalone VMDisk has no consumer to wait for, so on a
|
|
# WaitForFirstConsumer class the volume would otherwise never populate;
|
|
# the placement trade-off that buys is in README.md. Pinned per source so
|
|
# a regression to the old upload-only condition reddens here.
|
|
- equal:
|
|
path: metadata.annotations["cdi.kubevirt.io/storage.bind.immediate.requested"]
|
|
value: "true"
|
|
|
|
- it: renders a disk source as an in-namespace pvc clone
|
|
set:
|
|
source:
|
|
disk:
|
|
name: other
|
|
asserts:
|
|
- equal:
|
|
path: spec.source.pvc.name
|
|
value: vm-disk-other
|
|
- equal:
|
|
path: spec.source.pvc.namespace
|
|
value: tenant-test
|
|
- equal:
|
|
path: metadata.annotations["vm-disk.cozystack.io/source"]
|
|
value: '{"disk":{"name":"other"}}'
|
|
# Immediate binding is requested for every source type, not only upload.
|
|
# A standalone VMDisk has no consumer to wait for, so on a
|
|
# WaitForFirstConsumer class the volume would otherwise never populate;
|
|
# the placement trade-off that buys is in README.md. Pinned per source so
|
|
# a regression to the old upload-only condition reddens here.
|
|
- equal:
|
|
path: metadata.annotations["cdi.kubevirt.io/storage.bind.immediate.requested"]
|
|
value: "true"
|
|
|
|
- it: renders an upload source and requests immediate bind
|
|
set:
|
|
source:
|
|
upload: {}
|
|
asserts:
|
|
- equal:
|
|
path: spec.source.upload
|
|
value: {}
|
|
- equal:
|
|
path: metadata.annotations["cdi.kubevirt.io/storage.bind.immediate.requested"]
|
|
value: "true"
|
|
- equal:
|
|
path: metadata.annotations["vm-disk.cozystack.io/source"]
|
|
value: '{"upload":{}}'
|
|
|
|
- it: rejects more than one source type
|
|
set:
|
|
source:
|
|
http:
|
|
url: https://example.com/img.qcow2
|
|
image:
|
|
name: ubuntu
|
|
asserts:
|
|
- failedTemplate:
|
|
errorMessage: Exactly one type of source is expected!
|
|
|
|
- it: rejects an unrecognized single source type with a clear message
|
|
set:
|
|
source:
|
|
snapshot:
|
|
name: snap1
|
|
asserts:
|
|
- failedTemplate:
|
|
errorPattern: 'Unknown source type'
|