Flavors: REST API and behaviour — Nova — branch stable/2026.2
Branch
stable/2026.2@8df1922926dd(34.0.0+ 7 commits) — repository https://opendev.org/openstack/nova — analysed on 2026-10-28 Scope: flavors only. Maximum microversion of the branch: 2.104 (nova/api/openstack/api_version_request.py:300).
A flavor is a named, administrator-defined size of server: vCPUs, RAM, disks, plus free-form extra specs that drive scheduling and the hypervisor configuration. Flavors live in the API database (flavors, flavor_extra_specs, flavor_projects, 03). Each server keeps a frozen copy of its flavor (§ 6).
1. Endpoints
Routes: nova/api/openstack/compute/routes.py:394-420.
| Method and path | Handler | Policy (default) | Versions | Success |
|---|---|---|---|---|
GET /flavors | nova/api/openstack/compute/flavors.py:141-150 | none (any authenticated user) | all | 200, {flavors: [{id, name, links, description≥2.55}]} |
GET /flavors/detail | nova/api/openstack/compute/flavors.py:152-171 | none; extra_specs only if os_compute_api:os-flavor-extra-specs:index passes (≥ 2.61) | all | 200 |
GET /flavors/{id} | nova/api/openstack/compute/flavors.py:173-195 | none; same extra_specs rule | all | 200 |
POST /flavors | nova/api/openstack/compute/flavors.py:58-112 | os_compute_api:os-flavor-manage:create = admin | all | 200 (not 201) |
PUT /flavors/{id} | nova/api/openstack/compute/flavors.py:114-139 | os_compute_api:os-flavor-manage:update = admin | ≥ 2.55, description only | 200 |
DELETE /flavors/{id} | nova/api/openstack/compute/flavors.py:43-54 | os_compute_api:os-flavor-manage:delete = admin | all | 202 (not 204) |
GET /flavors/{flavor_id}/os-extra_specs | nova/api/openstack/compute/flavors_extraspecs.py:57-66 | …os-flavor-extra-specs:index = project reader or admin | all | 200 {extra_specs: {…}} |
GET /flavors/{flavor_id}/os-extra_specs/{key} | nova/api/openstack/compute/flavors_extraspecs.py:111-127 | …:show = project reader or admin | all | 200 {key: value} |
POST /flavors/{flavor_id}/os-extra_specs | nova/api/openstack/compute/flavors_extraspecs.py:71-88 | …:create = admin | all | 200, echoes the body |
PUT /flavors/{flavor_id}/os-extra_specs/{key} | nova/api/openstack/compute/flavors_extraspecs.py:90-109 | …:update = admin | all | 200 |
DELETE /flavors/{flavor_id}/os-extra_specs/{key} | nova/api/openstack/compute/flavors_extraspecs.py:132-149 | …:delete = admin | all | 200 (a code comment says it should be 204, nova/api/openstack/compute/flavors_extraspecs.py:129-131) |
GET /flavors/{flavor_id}/os-flavor-access | nova/api/openstack/compute/flavor_access.py:44-60 | os_compute_api:os-flavor-access = admin | all | 200 {flavor_access: [{flavor_id, tenant_id}]} |
POST /flavors/{id}/action addTenantAccess | nova/api/openstack/compute/flavor_access.py:67-91 | …os-flavor-access:add_tenant_access = admin | all | 200 |
POST /flavors/{id}/action removeTenantAccess | nova/api/openstack/compute/flavor_access.py:93-123 | …os-flavor-access:remove_tenant_access = admin | all | 200 |
Policies: nova/policies/flavor_manage.py:26-58, nova/policies/flavor_extra_specs.py:23-104, nova/policies/flavor_access.py:46-83. All are scope_types=['project']. "admin" is rule:context_is_admin (nova/policies/base.py:45).
2. Visibility: who sees which flavor
- Admin: every flavor.
is_publicin the query filters (true,false, ornonefor all) (nova/api/openstack/compute/flavors.py:220-223). - Anyone else: public flavors and private flavors whose access list contains their project (
nova/objects/flavor.py:283-288,nova/objects/flavor.py:630-638). In lists, disabled flavors are hidden too (nova/api/openstack/compute/flavors.py:225-226). - A private flavor outside the user's access list does not exist for them:
GET /flavors/{id}and every sub-resource answer 404 (nova/api/openstack/common.py:491-495).
3. The flavor document
nova/api/openstack/compute/views/flavors.py:43-75:
| Field | Source (flavors table) | Versions | Notes |
|---|---|---|---|
id | flavorid (string) | all | the public id; the integer id column is internal |
name | name | all | unique |
vcpus | vcpus | all | |
ram | memory_mb | all | MiB |
disk | root_gb | all | GiB; 0 = no local root disk limit (§ 5) |
OS-FLV-EXT-DATA:ephemeral | ephemeral_gb | all | GiB |
swap | swap | all | MiB; "" when 0 before 2.75, 0 from 2.75 (nova/api/openstack/compute/views/flavors.py:68-69) |
os-flavor-access:is_public | is_public | all | |
OS-FLV-DISABLED:disabled | disabled | < 2.102 | |
rxtx_factor | rxtx_factor | < 2.102 | "" when null |
description | description | ≥ 2.55 | |
extra_specs | flavor_extra_specs | ≥ 2.61, if the extra-specs index policy passes | |
links | — | all | self and bookmark |
vcpu_weight is stored but never shown by the API (nova/objects/flavor.py:221).
4. Creating, changing and deleting
Create (POST /flavors)
Request schema nova/api/openstack/compute/schemas/flavors.py:29-95:
name(required): 1-255 characters, no leading or trailing spaces, except in v2.0, which allows them (nova/api/openstack/compute/schemas/flavors.py:78-81); stripped (nova/compute/flavors.py:80-81).id(optional): string or number, 1-255 characters of[a-zA-Z0-9. _-], no leading or trailing space (nova/api/openstack/compute/schemas/flavors.py:38-42). Absent ornull: a UUID is generated (nova/compute/flavors.py:64-68). A number is stored as a string (nova/compute/flavors.py:85).ram,vcpus(required): integer ≥ 1;disk(required),OS-FLV-EXT-DATA:ephemeral,swap: integer ≥ 0; all ≤ 2,147,483,647 (nova/api/validation/parameter_types.py:470-497,nova/compute/flavors.py:90-101).rxtx_factor: float > 0, default 1.0; refused from 2.102 (nova/api/openstack/compute/schemas/flavors.py:94-95).os-flavor-access:is_public: boolean, defaulttrue.description(≥ 2.55): up to 65,535 characters.
The flavor is written with an admin context and a flavor.create notification is sent (nova/compute/flavors.py:124-126, nova/objects/flavor.py:575-596). Creating a private flavor does not give access to the caller's project (comment nova/api/openstack/compute/flavors.py:96). The response carries extra_specs: {} from 2.61 (nova/api/openstack/compute/flavors.py:101-109).
Update (PUT /flavors/{id}, ≥ 2.55)
**Only description can change** (nova/api/openstack/compute/schemas/flavors.py:97-115). The other fields are immutable: changing a size means creating a new flavor. A flavor.update notification is sent (nova/objects/flavor.py:550-552).
Delete (DELETE /flavors/{id})
The flavor, its extra specs and its access list are hard-deleted (nova/objects/flavor.py:179-196), then flavor.delete is notified. Servers using it are not touched: they keep their copy (§ 6), and the server API shows it from that copy (nova/api/openstack/compute/views/servers.py:665-684).
List (GET /flavors, /flavors/detail)
- Filters:
minRam,minDisk(non-integers → 400) (nova/api/openstack/compute/flavors.py:228-241),is_public(admin only), and **name, from 2.102, treated as a regular expression** by the database (nova/objects/flavor.py:620-628). - Sort:
sort_key(one of 15 columns;rxtx_factorrefused from 2.102) andsort_dir, defaultflavorid asc(nova/api/openstack/compute/schemas/flavors.py:21-27,nova/api/openstack/compute/flavors.py:215-216). - Paging:
limitandmarker(aflavorid; unknown marker → 400) (nova/objects/flavor.py:640-652). - Unknown query parameters: ignored before 2.75, refused (400) from 2.75 (
nova/api/openstack/compute/schemas/flavors.py:131-139).
5. Extra specs
Extra specs are key: value strings attached to a flavor. They change how and where the server runs: CPU pinning, huge pages, NUMA, devices, Placement resources and traits, scheduler filters.
Writing rules
- Keys:
^[a-zA-Z0-9-_:. ]{1,255}$; values: strings of up to 255 characters, numbers accepted and converted (nova/api/validation/parameter_types.py:431-439,nova/api/openstack/compute/flavors_extraspecs.py:41-52). POSTmerges the new keys into the existing ones (nova/api/openstack/compute/flavors_extraspecs.py:82).PUT …/{key}takes exactly one key, which must match the URL (nova/api/openstack/compute/flavors_extraspecs.py:98-100).- Validation from 2.86 (
nova/api/openstack/compute/flavors_extraspecs.py:39,nova/api/openstack/compute/flavors_extraspecs.py:54-55,nova/api/validation/extra_specs/validators.py:31-63):- a key known to the registry is checked against its type (integer with min and max, boolean "like"
strutils.bool_from_string, string with an enumeration or a pattern) (nova/api/validation/extra_specs/base.py:62-120); - a key without
:and unknown is accepted as is; - an unknown key in a namespace Nova owns (
hw:,quota:,trait…:,resources…:,capabilities:…) is refused:Validation failed; extra spec '<key>' does not appear to be a valid extra spec.(400); - an unknown key in another namespace (
my:key) is accepted.
- a key known to the registry is checked against its type (integer with min and max, boolean "like"
- Before 2.86, nothing is validated: any key and value are stored. Existing flavors may therefore hold values the API now refuses.
The registry
525 validators are loaded through the stevedore namespace nova.api.extra_spec_validators (nova/api/validation/extra_specs/validators.py:66-82). The full list, with types, allowed values and descriptions, is in _rewrite/extra-spec-validators_stable-2026.2.json.
| Namespace | Count | Defined in | What it controls |
|---|---|---|---|
hw: | 37 | nova/api/validation/extra_specs/hw.py | CPU policy and topology, realtime, NUMA, page size, memory encryption, TPM, devices (serial, sound, USB, watchdog, IOMMU), ephemeral encryption |
trait{group}: | 378 | nova/api/validation/extra_specs/traits.py:24-70 | one per standard trait plus CUSTOM_*: required / forbidden |
resources{group}: | 22 | nova/api/validation/extra_specs/resources.py:24-62 | Placement resource amounts (21 standard classes + CUSTOM_*) |
capabilities: | 45 | nova/api/validation/extra_specs/capabilities.py:58-108 | ComputeCapabilitiesFilter |
quota: | 31 | nova/api/validation/extra_specs/quota.py:24-185 | libvirt cgroup/disk/VIF QoS (15) and VMware limits/reservations/shares (16) |
hw_rng: | 3 | nova/api/validation/extra_specs/hw_rng.py | random number generator |
| others | 9 | nova/api/validation/extra_specs/accel.py, nova/api/validation/extra_specs/aggregate_instance_extra_specs.py, nova/api/validation/extra_specs/hw_video.py, nova/api/validation/extra_specs/os.py, nova/api/validation/extra_specs/pci_passthrough.py, nova/api/validation/extra_specs/vmware.py, nova/api/validation/extra_specs/null.py | Cyborg device profile, aggregate matching, video RAM cap, secure boot, PCI aliases, VMware, group_policy, hide_hypervisor_id |
What reads extra specs
- Placement request (
nova/scheduler/utils.py:80-203):resources*:andtrait*:keys are taken as is (nova/scheduler/utils.py:233-257). ThenVCPU,MEMORY_MBandDISK_GBare added unless overridden; disk = ephemeral + swap (rounded up to GiB) + root, the root being left out for boot-from-volume (nova/scheduler/utils.py:171-185). Several extra specs become resources or traits:hw:cpu_policy=dedicated→PCPUinstead ofVCPU, plus onePCPUforhw:emulator_threads_policy=isolate(nova/scheduler/utils.py:363-417);hw:cpu_thread_policy→ traitHW_CPU_HYPERTHREADINGforbidden or required (nova/scheduler/utils.py:419-427);hw:mem_encryption→MEM_ENCRYPTION_CONTEXT=1plus the model's trait (nova/scheduler/utils.py:320-342);os:secure_boot, the vTPM andhw:pmem→ traits or resources (nova/scheduler/utils.py:269-361).
- Flavor and image properties: most
hw:<x>keys have an image twinhw_<x>(nova/virt/hardware.py:1344-1358). Conflicts are refused, with three rules:- an image cannot request CPU pinning that the flavor did not grant (
ImageCPUPinningForbidden,nova/virt/hardware.py:1776-1823); - image topology limits must stay within the flavor's (
nova/virt/hardware.py:532-566); - several keys must be equal on both sides (
FlavorImageConflict,nova/virt/hardware.py:1361-1390).
- an image cannot request CPU pinning that the flavor did not grant (
- Scheduler filters:
aggregate_instance_extra_specs:*andcapabilities:*. - The libvirt driver: devices, cgroups (
quota:*), firmware.
6. The flavor inside a server
- At boot, the flavor must exist and not be disabled (
nova/compute/api.py:1131-1132). Its RAM and disk must fit the image'smin_ramandmin_disk, and the image size whendisk > 0(nova/compute/api.py:814-885).disk = 0with an image-backed root may be refused (BootFromVolumeRequiredForZeroDiskFlavor, 403). - The server stores its own copy in
instance_extra.flavor, a JSON{"cur": …, "old": …, "new": …}of versionedFlavorobjects (nova/objects/instance.py:749-762):curis the current size;oldandneware only set during a resize;- the
descriptionis blanked in the copy (nova/objects/instance.py:745-747). Changing or deleting the flavor afterwards has no effect on existing servers. Very old instances missdisabledandis_public, which are defaulted when the copy is loaded (nova/objects/instance.py:349-379).
- The server document shows the flavor from 2.47 as an embedded object:
vcpus,ram,disk,ephemeral,swap,original_name, andextra_specsif allowed. Before 2.47 it shows only the flavoridand link (nova/api/openstack/compute/views/servers.py:652-684).
7. Resize
nova/compute/api.py:4295-4440:
- The new flavor is looked up without deleted rows, and only among flavors visible to the caller (
nova/compute/api.py:4345-4346). - A flavor with
disk = 0is refused for an image-backed server:Resize to zero disk flavor is not allowed.(nova/compute/api.py:4355-4361). - A disabled target flavor is refused, unless it is the current flavor (cold migration) (
nova/compute/api.py:4371-4376). Resizing to the same flavor is refused (CannotResizeToSameFlavor,nova/compute/api.py:4378-4379). - The quota for the size increase is checked, then the image constraints are checked again against the new flavor (
nova/compute/api.py:4389-4419). The NUMA topology and PCI requests are recomputed from the new flavor (nova/compute/api.py:4425-4438). - During the resize, the server holds
old_flavorandnew_flavor. Confirm keepsnewascur; revert restoresold[inferred: done by the compute manager, not read here].
The HTTP translation is in nova/api/openstack/compute/servers.py:1200-1209: flavor not found, same flavor and disk refusal → 400; flavor/image conflicts (INVALID_FLAVOR_IMAGE_EXCEPTIONS, nova/api/openstack/compute/servers.py:55-93) → 400.
8. Lifecycle
stateDiagram-v2
[*] --> Public: POST /flavors (is_public true)
[*] --> Private: POST /flavors (is_public false)
Private --> Private: addTenantAccess / removeTenantAccess
Public --> Public: PUT description, extra specs changes
Private --> Private: PUT description, extra specs changes
Public --> [*]: DELETE (servers keep their copy)
Private --> [*]: DELETE (servers keep their copy)disabled cannot be set through the API: it only exists in the database (and was only shown before 2.102).
Source: reverse-spec-nova/01-flavors-api_stable-2026.2.md. The _rewrite/ and _inventory/ files are not published on the site.