OpenKeel: the sovereign French Cloud solution of the inherent group, with a European reach Discover inherent

Reverse-spec · Services

nova — Flavors kubevirt mapping

Flavor ↔ KubeVirt instancetype mapping — Nova — branch stable/2026.2

Nova branch stable/2026.2 @ 8df1922926dd — KubeVirt v1.9.1 @ c3d19093f9c7 (instancetype.kubevirt.io/v1beta1, the only version of that API in this release; sparse clone of staging/src/kubevirt.io/api) — analysed on 2026-10-28 References: nova/… = Nova repository; kubevirt:… = KubeVirt repository at the tag above.

This file says how OpenKeel can expose the Nova flavor API unchanged while running servers as KubeVirt VirtualMachines. It gives the mapping field by field and extra spec by extra spec. Every rule is also data (_rewrite/flavor-kubevirt-mapping_stable-2026.2.json) and code (_rewrite/flavor-to-kubevirt_stable-2026.2.py). The converter was run on 6 reference flavors (_rewrite/flavor-kubevirt-vectors_stable-2026.2.json).

1. Object model

NovaKubeVirtWhy it fits
Public flavorVirtualMachineClusterInstancetype (kubevirt:staging/src/kubevirt.io/api/instancetype/v1beta1/types.go:56-62)cluster-wide, read by every namespace
Private flavor + flavor_projectsone VirtualMachineInstancetype per project namespace (kubevirt:staging/src/kubevirt.io/api/instancetype/v1beta1/types.go:34-40)namespaced objects give the same visibility as the access list
Flavor hardware hints that the image may also set (hw:cpu_sockets, os:secure_boot, devices…)VirtualMachine(Cluster)Preference with the same name (kubevirt:staging/src/kubevirt.io/api/instancetype/v1beta1/types.go:203-316)preferences are what KubeVirt applies when the VM does not say otherwise
instance_extra.flavor cur (frozen copy, 01 § 6)VirtualMachine.spec.instancetype.revisionName, a ControllerRevision (kubevirt:staging/src/kubevirt.io/api/core/v1/types.go:3890-3901)KubeVirt stores the instancetype as it was when the VM started; deleting or changing the instancetype does not change running VMs
Resize (old_flavor / new_flavor)change spec.instancetype.name (and clear revisionName), then restart; keep the previous name and revision to revertthere is no two-phase confirm in KubeVirt: OpenKeel keeps old itself
Flavor immutability (only description changes)change of the instancetype spec = new revision for new VMs onlysame behaviour if OpenKeel never updates the spec
Extra specs not translatedannotation openkeel.io/extra-specs (JSON)lossless round trip of GET /flavors/{id}/os-extra_specs

KubeVirt has no disabled flag, no access-list API and no "list sizes visible to a project" query merging both kinds. OpenKeel keeps the flavor catalogue as its source of truth, for example in its own database or as an annotation on the instancetype, and reconciles the KubeVirt objects from it.

2. Base fields

Nova field (API name)Nova sourceKubeVirt targetFidelityTranslation and notes
id (flavorid)nova/api/openstack/compute/schemas/flavors.py:38-42metadata.name + annotation openkeel.io/flavor-idexactNova allows [a-zA-Z0-9. _-], up to 255 characters; Kubernetes requires a lower-case DNS-1123 name. Ids that are already valid are kept; others become fl-<16 hex of sha1(id)>.
namenova/db/api/models.py:233annotation openkeel.io/flavor-nameexactunique in Nova (nova/db/api/models.py:229-230): OpenKeel must keep that check.
descriptionnova/api/openstack/compute/flavors.py:114-139annotation openkeel.io/flavor-descriptionexactthe only mutable field; an annotation change does not create a new revision.
vcpusnova/api/openstack/compute/views/flavors.py:53spec.cpu.guest (kubevirt:staging/src/kubevirt.io/api/instancetype/v1beta1/types.go:139)exact
ram (MiB)nova/api/openstack/compute/views/flavors.py:48spec.memory.guest: "<ram>Mi" (kubevirt:staging/src/kubevirt.io/api/instancetype/v1beta1/types.go:178)exactthe virt-launcher pod adds its overhead on top, as libvirt does on the hypervisor.
disk (GiB)nova/api/openstack/compute/views/flavors.py:49the VM's root dataVolumeTemplates[].spec.storage.resources.requests.storagepartialno disk in an instancetype. 0 = image size, or boot from volume only (02 § 4).
OS-FLV-EXT-DATA:ephemeral (GiB)nova/api/openstack/compute/views/flavors.py:51VM volume emptyDisk.capacitypartialone disk; Nova can split it with block device mappings.
swap (MiB)nova/api/openstack/compute/views/flavors.py:50VM emptyDisk + cloud-init mkswap/swaponapproximateno swap device in KubeVirt.
os-flavor-access:is_publicnova/api/openstack/compute/views/flavors.py:54kind: Cluster vs namespacedexact
access list (flavor_projects)nova/objects/flavor.py:72-92one namespaced copy per projectexactaddTenantAccess / removeTenantAccess = create or delete the copy.
OS-FLV-DISABLED:disablednova/compute/api.py:1131-1132annotation openkeel.io/disablednoneOpenKeel must refuse new boots and resizes itself (admission webhook or API layer).
rxtx_factornova/api/openstack/compute/views/flavors.py:55annotation onlyn/aremoved from the API at 2.102.
vcpu_weightnova/objects/flavor.py:221nonen/anot exposed by the API.
extra_specsnova/api/openstack/compute/views/flavors.py:65-66annotation openkeel.io/extra-specs + § 3exact (round trip)

3. Extra specs

Coverage: the 525 validators of Nova's registry (_rewrite/extra-spec-validators_stable-2026.2.json) all match a rule below (checked by script, validator_coverage in the mapping JSON). By fidelity:

exactpartialapproximatenode-levelnonen/a
84589101822

The 458 "partial" rules are mostly the 378 traits and 45 capabilities: they become node labels, and that only works if an OpenKeel agent puts the same labels on the nodes.

Keys that are not in the registry (allowed by Nova outside its namespaces) are kept in the annotation and reported as unknown by the converter.

Nova extra specAllowed valuesDefined atKubeVirt targetFidelityTranslation and notes
hw:cpu_policystr: dedicated, shared, mixednova/api/validation/extra_specs/hw.py:65spec.cpu.dedicatedCPUPlacement (kubevirt:staging/src/kubevirt.io/api/instancetype/v1beta1/types.go:152)partialdedicated → dedicatedCPUPlacement: true (exact; needs the kubelet static CPU manager); shared → nothing (default); mixed → none: KubeVirt cannot mix pinned and floating vCPUs
hw:cpu_dedicated_maskstr (pattern)nova/api/validation/extra_specs/hw.py:131nonenoneOnly meaningful with hw:cpu_policy=mixed.
hw:cpu_thread_policystr: prefer, isolate, requirenova/api/validation/extra_specs/hw.py:89kubelet CPU manager option full-pcpus-onlynode-levelprefer → default; require → node-level: full-pcpus-only, or nodeSelector on SMT nodes; isolate → none: nodeSelector on nodes without SMT is the closest Nova turns it into the HW_CPU_HYPERTHREADING trait (nova/scheduler/utils.py:419-427).
hw:emulator_threads_policystr: isolate, sharenova/api/validation/extra_specs/hw.py:110spec.cpu.isolateEmulatorThread (kubevirt:staging/src/kubevirt.io/api/instancetype/v1beta1/types.go:161)partialisolate → isolateEmulatorThread: true (one extra dedicated CPU, like Nova); share → none: Nova's cpu_shared_set has no KubeVirt equivalent
hw:cpu_realtimeboolnova/api/validation/extra_specs/hw.py:23spec.cpu.realtime: {} (kubevirt:staging/src/kubevirt.io/api/instancetype/v1beta1/types.go:165)exactRequires dedicatedCPUPlacement in both.
hw:cpu_realtime_maskstr (pattern)nova/api/validation/extra_specs/hw.py:35spec.cpu.realtime.mask (kubevirt:staging/src/kubevirt.io/api/instancetype/v1beta1/types.go:165)exactNova: exclusion mask such as '^0-1'; KubeVirt: inclusion mask in libvirt syntax. Converted to '0-<vcpus-1>,<mask>'.
hw:cpu_socketsint min 1nova/api/validation/extra_specs/hw.py:279preference spec.cpu.preferredCPUTopology (kubevirt:staging/src/kubevirt.io/api/instancetype/v1beta1/types.go:371)partialAn instancetype only has a vCPU count; the preference chooses sockets, cores or threads ('preferSockets'…) or a spread ratio, not exact counts.
hw:cpu_coresint min 1nova/api/validation/extra_specs/hw.py:293preference spec.cpu.preferredCPUTopology (kubevirt:staging/src/kubevirt.io/api/instancetype/v1beta1/types.go:371)partial—
hw:cpu_threadsint min 1nova/api/validation/extra_specs/hw.py:307preference spec.cpu.preferredCPUTopology (kubevirt:staging/src/kubevirt.io/api/instancetype/v1beta1/types.go:371)partial—
hw:cpu_max_socketsint min 1nova/api/validation/extra_specs/hw.py:321spec.cpu.maxSockets (kubevirt:staging/src/kubevirt.io/api/instancetype/v1beta1/types.go:169)approximateNova: upper bound when choosing the topology. KubeVirt: socket count reachable by CPU hotplug.
hw:cpu_max_coresint min 1nova/api/validation/extra_specs/hw.py:337nonenone—
hw:cpu_max_threadsint min 1nova/api/validation/extra_specs/hw.py:353nonenone—
hw:numa_nodesint min 1nova/api/validation/extra_specs/hw.py:182spec.cpu.numa.guestMappingPassthrough: {} (kubevirt:staging/src/kubevirt.io/api/instancetype/v1beta1/types.go:156)partialKubeVirt copies the host NUMA layout of the pinned CPUs; it cannot build N nodes. Needs dedicatedCPUPlacement and hugepages.
hw:numa_cpus.{num}str (pattern)nova/api/validation/extra_specs/hw.py:197nonenone—
hw:numa_mem.{num}int min 1nova/api/validation/extra_specs/hw.py:223nonenone—
hw:mem_page_sizestr (pattern)nova/api/validation/extra_specs/hw.py:153spec.memory.hugepages.pageSize (kubevirt:staging/src/kubevirt.io/api/instancetype/v1beta1/types.go:182)partialsmall → nothing (default); any → nothing: KubeVirt does not choose for you; large → approximate: '2Mi'; <size> → exact: '2Mi', '1Gi'… (a bare number is KiB in Nova)
hw:locked_memoryboolnova/api/validation/extra_specs/hw.py:167nonenone—
hw:mem_encryptionboolnova/api/validation/extra_specs/hw.py:399spec.launchSecurity (kubevirt:staging/src/kubevirt.io/api/core/v1/schema.go:856-860)exacttrue without a model means AMD SEV in Nova (nova/virt/hardware.py:1488-1489).
hw:mem_encryption_modelstr: amd-sev, amd-sev-es, amd-sev-snp, intel-tdxnova/api/validation/extra_specs/hw.py:411spec.launchSecurity (kubevirt:staging/src/kubevirt.io/api/core/v1/schema.go:856-860)exactamd-sev → launchSecurity.sev: {}; amd-sev-es → launchSecurity.sev.policy.encryptedState: true; amd-sev-snp → launchSecurity.snp: {}; intel-tdx → launchSecurity.tdx: {}
hw:pmemstr (pattern)nova/api/validation/extra_specs/hw.py:429nonenone—
hw:pmuboolnova/api/validation/extra_specs/hw.py:445nonenone—
hw:serial_port_countint min 0nova/api/validation/extra_specs/hw.py:467preference devices.preferredAutoattachSerialConsole (kubevirt:staging/src/kubevirt.io/api/instancetype/v1beta1/types.go:436)partial0 → false; >=1 → true (KubeVirt attaches one serial console)
hw:sound_modelstr: sb16, es1370, pcspk, ac97, ich6, ich9, usb, virtionova/api/validation/extra_specs/hw.py:578preference devices.preferredSoundModel (kubevirt:staging/src/kubevirt.io/api/instancetype/v1beta1/types.go:456)partialich9 → ich9; ac97 → ac97; others → none: KubeVirt supports ich9 and ac97
hw:tpm_modelstr: tpm-tis, tpm-crbnova/api/validation/extra_specs/hw.py:479preference devices.preferredTPM: {} (kubevirt:staging/src/kubevirt.io/api/instancetype/v1beta1/types.go:531)partialKubeVirt emulates a single TPM 2.0 model.
hw:tpm_versionstr: 1.2, 2.0nova/api/validation/extra_specs/hw.py:494preference devices.preferredTPM: {} (kubevirt:staging/src/kubevirt.io/api/instancetype/v1beta1/types.go:531)partial2.0 → preferredTPM: {}; 1.2 → none
hw:tpm_secret_securitystr: user, host, deploymentnova/api/validation/extra_specs/hw.py:511preference devices.preferredTPM.persistent (kubevirt:staging/src/kubevirt.io/api/instancetype/v1beta1/types.go:531)approximateNova chooses who holds the vTPM secret; KubeVirt only chooses whether the TPM state persists (PVC).
hw:usb_modelstr: none, nec-xhci, qemu-xhcinova/api/validation/extra_specs/hw.py:591nonenone—
hw:redirected_usb_portsint min 0, max 15nova/api/validation/extra_specs/hw.py:604VirtualMachine devices.clientPassthrough: {} (kubevirt:staging/src/kubevirt.io/api/core/v1/schema.go:604)partialKubeVirt enables USB redirection with a fixed number of ports.
hw:vif_multiqueue_enabledboolnova/api/validation/extra_specs/hw.py:385preference devices.preferredNetworkInterfaceMultiQueue (kubevirt:staging/src/kubevirt.io/api/instancetype/v1beta1/types.go:526)exact—
hw:virtio_packed_ringboolnova/api/validation/extra_specs/hw.py:566nonenone—
hw:viommu_modelstr: intel, smmuv3, virtio, autonova/api/validation/extra_specs/hw.py:550nonenone—
hw:watchdog_actionstr: none, pause, poweroff, reset, disablednova/api/validation/extra_specs/hw.py:528VirtualMachine devices.watchdog.i6300esb.action (kubevirt:staging/src/kubevirt.io/api/core/v1/schema.go:1408-1431)partialpoweroff → poweroff; reset → reset; pause → none; none → watchdog without action: none; disabled → no watchdog
hw:boot_menuboolnova/api/validation/extra_specs/hw.py:374nonenone—
hw:hide_hypervisor_idboolnova/api/validation/extra_specs/hw.py:50preference features.preferredKvm.hidden (kubevirt:staging/src/kubevirt.io/api/instancetype/v1beta1/types.go:570)exact—
hide_hypervisor_idboolnova/api/validation/extra_specs/null.py:22preference features.preferredKvm.hidden (kubevirt:staging/src/kubevirt.io/api/instancetype/v1beta1/types.go:570)exactDeprecated alias of hw:hide_hypervisor_id.
hw:ephemeral_encryptionboolnova/api/validation/extra_specs/hw.py:621StorageClass of the ephemeral volumenode-level—
hw:ephemeral_encryption_formatstr: plain, luks, luksv2nova/api/validation/extra_specs/hw.py:631StorageClass of the ephemeral volumenode-level—
hw:pci_numa_affinity_policystr: required, preferred, legacy, socketnova/api/validation/extra_specs/hw.py:249kubelet Topology Manager policynode-level—
hw_rng:allowedboolnova/api/validation/extra_specs/hw_rng.py:23preference devices.preferredRng: {} (kubevirt:staging/src/kubevirt.io/api/instancetype/v1beta1/types.go:516)partialNova only allows the image to ask for an RNG; KubeVirt attaches one.
hw_rng:rate_bytesint min 0nova/api/validation/extra_specs/hw_rng.py:35nonenone—
hw_rng:rate_periodint min 0nova/api/validation/extra_specs/hw_rng.py:46nonenone—
hw_video:ram_max_mbint min 0nova/api/validation/extra_specs/hw_video.py:23nonenone—
os:secure_bootstr: disabled, requirednova/api/validation/extra_specs/os.py:24preference firmware.preferredEfi.secureBoot (kubevirt:staging/src/kubevirt.io/api/instancetype/v1beta1/types.go:615)exactrequired → preferredEfi: {secureBoot: true} + features.preferredSmm: {}; disabled → preferredEfi: {secureBoot: false}
quota:cpu_sharesint min 0nova/api/validation/extra_specs/quota.py:99none (cgroup weight comes from the pod requests)none—
quota:cpu_periodint min 0nova/api/validation/extra_specs/quota.py:116nonenone—
quota:cpu_quotaintnova/api/validation/extra_specs/quota.py:131VirtualMachine resources.limits.cpuapproximatelimit = vcpus x cpu_quota / cpu_period.
quota:disk_*_sec(family)nova/api/validation/extra_specs/quota.py:150-168StorageClass / CSI QoSnode-level—
quota:vif_*(family)nova/api/validation/extra_specs/quota.py:170-185pod annotations kubernetes.io/ingress-bandwidth / egress-bandwidth (CNI bandwidth plugin)approximateOnly the pod network; average in KB/s becomes '<8 x n>k'.
quota:{cpu,memory,disk_io,vif}_{limit,reservation,shares_level,shares_share}(family)nova/api/validation/extra_specs/quota.py:24-94nonen/aVMware driver only.
vmware:*(family)nova/api/validation/extra_specs/vmware.py:20-44nonen/a—
resources{group}:PCPUintnova/api/validation/extra_specs/resources.py:24-39spec.cpu.dedicatedCPUPlacement: true (kubevirt:staging/src/kubevirt.io/api/instancetype/v1beta1/types.go:152)partialExact when PCPU = vcpus; a VCPU/PCPU split is 'mixed', which KubeVirt lacks.
resources{group}:VGPUintnova/api/validation/extra_specs/resources.py:24-39spec.gpus[] (deviceName = mediated device resource) (kubevirt:staging/src/kubevirt.io/api/instancetype/v1beta1/types.go:102)partialNeeds the mdev type -> device plugin resource name in the KubeVirt CR permittedHostDevices.
resources{group}:MEM_ENCRYPTION_CONTEXTintnova/api/validation/extra_specs/resources.py:24-39implied by spec.launchSecurity (kubevirt:staging/src/kubevirt.io/api/core/v1/schema.go:856-860)partial—
resources{group}:VCPU, MEMORY_MB, DISK_GB(family)nova/api/validation/extra_specs/resources.py:24-39none (overrides the Placement request only)n/a—
resources{group}:<other standard class>(family)nova/api/validation/extra_specs/resources.py:24-62spec.hostDevices[] / VM resources.requests (device plugin resource) (kubevirt:staging/src/kubevirt.io/api/instancetype/v1beta1/types.go:108)partialPGPU, SRIOV_NET_VF, FPGA, PMEM_NAMESPACE_*, NET_BW_*…: each needs a device plugin or an extended resource; CUSTOM_* likewise.
trait{group}:<TRAIT>(family)nova/api/validation/extra_specs/traits.py:24-70required: spec.nodeSelector['openkeel.io/trait.<TRAIT>'] = 'true'; forbidden: VM nodeAffinity NotIn (kubevirt:staging/src/kubevirt.io/api/instancetype/v1beta1/types.go:83)partialTraits become node labels set by an OpenKeel node agent; an instancetype has nodeSelector but no affinity, so 'forbidden' goes to the VirtualMachine.
group_policystr: isolate, nonenova/api/validation/extra_specs/null.py:38nonen/aPlacement granular request policy.
aggregate_instance_extra_specs:{key}strnova/api/validation/extra_specs/aggregate_instance_extra_specs.py:53spec.nodeSelector['openkeel.io/aggregate.<key>'] = <value> (kubevirt:staging/src/kubevirt.io/api/instancetype/v1beta1/types.go:83)partialPlain values only; the filter's operators (s==, <in>, <or>…) need node affinity.
capabilities:*(family)nova/api/validation/extra_specs/capabilities.py:58-108spec.nodeSelector on node labels (kubevirt:staging/src/kubevirt.io/api/instancetype/v1beta1/types.go:83)partialComputeCapabilitiesFilter operators need node affinity.
pci_passthrough:aliasstr (pattern)nova/api/validation/extra_specs/pci_passthrough.py:22spec.hostDevices[] (one per device) (kubevirt:staging/src/kubevirt.io/api/instancetype/v1beta1/types.go:108)partial'<alias>:<count>'; the [pci] alias must map to a permittedHostDevices resource.
accel:device_profilestrnova/api/validation/extra_specs/accel.py:22none (Cyborg)none—

Notes on the important translations

  • CPU pinning. hw:cpu_policy=dedicated maps exactly to dedicatedCPUPlacement, but KubeVirt needs the kubelet static CPU manager and Guaranteed QoS on the node. mixed has no equivalent. Nova also converts dedicated vCPUs into PCPU resources (nova/scheduler/utils.py:363-417); in KubeVirt, the kubelet CPU manager does that accounting.
  • Realtime mask. Nova's mask excludes vCPUs (^0-1), KubeVirt's mask lists them in libvirt syntax (kubevirt:staging/src/kubevirt.io/api/core/v1/schema.go:393-398). The converter writes 0-<vcpus-1>,<nova mask>.
  • Huge pages. A bare number is KiB in Nova (nova/virt/hardware.py:1572-1573). 1GB → 1Gi, 2048 → 2Mi. large has no "best available" equivalent: the converter picks 2Mi.
  • NUMA. KubeVirt can only pass through the host layout of the pinned CPUs (kubevirt:staging/src/kubevirt.io/api/core/v1/schema.go:403-410). hw:numa_nodes, hw:numa_cpus.N and hw:numa_mem.N lose their exact layout.
  • Memory encryption is the best fit: the four Nova models map to sev, sev + encryptedState, snp and tdx (kubevirt:staging/src/kubevirt.io/api/core/v1/schema.go:856-893). Nova's default model, when only hw:mem_encryption=true is set, is AMD SEV (nova/virt/hardware.py:1488-1489).
  • Traits and aggregates. An instancetype has a nodeSelector (kubevirt:staging/src/kubevirt.io/api/instancetype/v1beta1/types.go:83) but no affinity. So:
    • required traits and plain aggregate values go there;
    • forbidden traits and aggregate operators (<in>, s>=…) must go into the VirtualMachine's affinity.
  • Devices. PCI aliases and vGPUs need an OpenKeel configuration mapping each Nova [pci] alias and mdev type to a permittedHostDevices resource name.
  • QoS. Disk and network QoS move to the StorageClass and the CNI. CPU shares follow the pod requests, which KubeVirt derives from vcpus / cpuAllocationRatio [inferred: KubeVirt cluster configuration, not part of the sparse clone].

4. Image properties that end in the preference

Most hw:<x> extra specs have an image twin hw_<x>, and Nova refuses conflicts (01 § 5). In KubeVirt, a single preference holds both:

  • OpenKeel builds the preference from the flavor's extra specs, then the image properties;
  • it refuses the boot, as Nova does, when the two disagree, applying Nova's rules: nova/virt/hardware.py:1361-1390 for strict equality, and nova/virt/hardware.py:1776-1823 for an image that cannot raise the flavor's CPU policy.

Image-only properties (not flavors) with a direct preference target [inferred: not part of this flavor scope, listed to complete the preference]:

Image propertyPreference field
hw_disk_busdevices.preferredDiskBus
hw_cdrom_busdevices.preferredCdromBus
hw_vif_modeldevices.preferredInterfaceModel
hw_video_modeldevices.preferredVideoType
hw_input_bus / hw_pointer_modeldevices.preferredInputBus / preferredInputType
hw_machine_typemachine.preferredMachineType
hw_firmware_type=uefifirmware.preferredEfi
hw_rng_modeldevices.preferredRng
os_secure_bootfirmware.preferredEfi.secureBoot
hw_architecturepreferredArchitecture / requirements.architecture
min_ramrequirements.memory.guest

requirements.cpu.guest and requirements.memory.guest (kubevirt:staging/src/kubevirt.io/api/instancetype/v1beta1/types.go:641-669) make KubeVirt refuse an instancetype that is too small for the preference. That matches Nova's FlavorMemoryTooSmall check against the image min_ram.

5. What OpenKeel puts in each VirtualMachine

The converter also returns a VM fragment:

  • spec.instancetype / spec.preference → {kind, name};
  • the root disk size (disk, or the image size when disk = 0);
  • the ephemeral and swap emptyDisks;
  • what has no place in an instancetype: watchdog, USB redirection, CPU limit from quota:cpu_quota, bandwidth annotations, NotIn affinity for forbidden traits.

The server API must keep showing the Nova flavor:

  • from 2.47, server.flavor = {original_name, vcpus, ram, disk, ephemeral, swap, extra_specs} taken from the revision and the annotations, not from the current instancetype (nova/api/openstack/compute/views/servers.py:652-684);
  • before 2.47, {id, links}.

6. Reference flavors (vectors)

_rewrite/flavor-kubevirt-vectors_stable-2026.2.json, produced by flavor-to-kubevirt_stable-2026.2.py --vectors:

VectorInstancetype resultNot translated
m1.small, public, no extra specscluster instancetype 2: cpu.guest 1, memory.guest 2048Mi; root disk 20Gi—
private, UUID id, 100 GiB ephemeral, 4 GiB swap, multiqueue2 namespaced instancetypes + preferences (proj-a, proj-b); root disk = image size; ephemeral and swap emptyDisksswap is an approximation
NFV: dedicated, require, isolate, 1GB pages, 1 NUMA node, realtime ^0-1dedicatedCPUPlacement, isolateEmulatorThread, realtime.mask 0-7,^0-1, hugepages 1Gi, numa.guestMappingPassthroughhw:cpu_thread_policy, hw:pci_numa_affinity_policy (node-level)
confidential: SEV-SNP, secure boot, vTPM 2.0, hidden hypervisorlaunchSecurity.snp; preference preferredEfi.secureBoot, preferredSmm, preferredTPM, preferredKvm.hidden—
GPU + PCI + traits + aggregatesgpus[1], hostDevices[2] (resource names to configure), nodeSelector for the required trait and ssd=true; VM NotIn affinity for the forbidden traitaggregate operator <in> r1
QoS and devicespreference preferSockets, serial console; VM watchdog reset, CPU limit 1000m, ingress-bandwidth 81920kquota:disk_read_iops_sec (StorageClass), hw:sound_model=virtio, hw:locked_memory, my-own-key (kept)

7. Gaps and recommendations

  1. No exact equivalent:
    • hw:cpu_policy=mixed;
    • an explicit NUMA layout;
    • hw:cpu_thread_policy per VM;
    • swap;
    • per-flavor disk and VIF QoS;
    • hw:locked_memory, hw:pmem, hw:pmu, hw:viommu_model, hw:usb_model, hw:boot_menu, hw:virtio_packed_ring, hw_rng rates, hw_video:ram_max_mb;
    • accel:device_profile (Cyborg). Announce them as compatibility gaps of OpenKeel. Keep the keys, so that the API round trip stays exact.
  2. Validate like Nova ≥ 2.86 with the registry file: same types, enumerations, patterns and namespaces. Refuse what Nova refuses, and accept what Nova accepts even if KubeVirt ignores it.
  3. Keep Placement semantics where KubeVirt has none. Quotas count vcpus, ram and disk of the flavor. They are not Kubernetes ResourceQuota requests, which are divided by the CPU allocation ratio.
  4. Node labels for traits, aggregates and capabilities must be published by an OpenKeel node agent with a fixed prefix (openkeel.io/trait.<TRAIT>, openkeel.io/aggregate.<key>).
  5. Names: keep the Nova name uniqueness check and the id → Kubernetes name encoding stable. The encoding is part of the contract between OpenKeel components.

8. Checklist for the OpenKeel flavor service

  • [ ] Flavor API of 01, up to 2.104, with the same status codes (200 on create, 202 on delete, 200 on extra-spec delete) and messages of 02.
  • [ ] Visibility rules (public + access list; admin is_public filter; disabled hidden).
  • [ ] Extra-spec validation from the registry file (≥ 2.86 only).
  • [ ] Reconciler: flavor → (Cluster)Instancetype / Preference per the rules; delete leaves VMs untouched.
  • [ ] Server view of the flavor from the VM's revision.
  • [ ] Resize: switch the instancetype, keep old for revert, refuse the cases of 01 § 7.
  • [ ] Versioned notifications flavor.create/update/delete with FlavorPayload 1.4, if consumers rely on them.

Source: reverse-spec-nova/07-flavors-kubevirt-mapping_stable-2026.2.md. The _rewrite/ and _inventory/ files are not published on the site.