Context
The scenario renderer (app/core/scenario_renderer/) turns a canvas-authored
ScenarioSpec into a concrete bundle-scenario directory that the existing deployer-cli
deploys unchanged. It is complete except for one input the canvas cannot supply:
vmids and IPs.
Without an allocator, a UI-authored scenario will collide with demo_lab (or any other
scenario) on a shared Proxmox. This is the last piece before a UI-authored scenario can
be deployed live.
The ledger it must allocate against
range42-playbooks scenarios/_reserved.json (on dev) is the global reservation
ledger: NDJSON, one entry per VM and per template, regenerated from every scenario's
manifest/scenario_vms.json by scenarios/_regenerate_reserved.sh, and validated by
scenarios/_check_reserved.sh (8 rules: vmid collisions, (bridge, ip) collisions,
template consistency across scenarios, and cross-role collisions).
Currently 192 entries. Existing scenarios each own a contiguous vmid block —
demo_lab=11xx, blank_scenario_2=2xxx, blank_scenario_4=4xxx,
blank_scenario_6=6xxx, debug_*=8xxx, _init_lab=9xx, templates=9xxx — and 13 shared
9xxx template rows are duplicated byte-identically into every scenario that uses them
(this is what _check_reserved.sh rule 5 enforces).
Requirements
Pure function. allocate(draft, reserved_entries) -> ScenarioSpec. No git, no
filesystem, no network inside app/core/. The caller parses the ledger and passes it in.
Fully unit-testable.
Per-scenario vmid block. UI-authored scenarios allocate from a dedicated range
(proposal: 3000-3999, 100-wide blocks), taking the lowest free block. Keeps the ledger
legible and keeps a scenario that grows by two VMs from scattering across the space.
Pin on re-render. The allocator takes the scenario's previous manifest as an optional
input and keeps every surviving vm_name's vmid/IP byte-identical. Only genuinely new
VMs get fresh allocations; removed VMs free their slots. Without this, a canvas edit
renumbers live VMs and the deployer destroys and recreates them.
Multi-NIC-native internal model. The unit of IP allocation must be (vm, nic), not
(vm). Segmented topologies (internet / DMZ / office LANs) need a firewall or router VM
with a leg in each segment, and building the allocator single-NIC means rewriting it
later. See the manifest-schema issue for what can actually be emitted today — the
internal model should be ahead of the emitted schema, not behind it.
No hardcoded address assumptions. Do not assume 192.168.x or /24. Bridge, CIDR and
gateway are inputs (canvas network nodes already carry cidr_template / bridge_template,
and attachments carry ip / ip_template). Use ipaddress from stdlib; reserve the
network address, the broadcast address and the gateway. This keeps the allocator working
unchanged when networks move to SDN VNets with arbitrary addressing.
Template rows. Copy the shared 9xxx template rows for the templates the scenario's
VMs actually reference, verbatim from the ledger, so _check_reserved.sh rule 5 stays
green.
Loud failure. Raise on an exhausted block range, on a pinned vmid that a third party
has since taken, and on any intra-scenario collision. Never silently renumber.
Notes
app/core/scenario_renderer/manifest.py already enforces the intra-scenario half of the
collision rules (_validate: duplicate vm_name, duplicate vm_id, duplicate
(bridge, ip), VM-vs-template vmid collision). The allocator should make those unreachable
rather than duplicate them.
Acceptance
- Allocates a collision-free scenario against the real 192-entry ledger.
- Re-allocating after adding/removing a VM leaves every surviving VM's vmid and IP unchanged.
- The generated
manifest/scenario_vms.json, dropped into range42-playbooks/scenarios/,
passes _check_reserved.sh with no failures.
Context
The scenario renderer (
app/core/scenario_renderer/) turns a canvas-authoredScenarioSpecinto a concrete bundle-scenario directory that the existing deployer-clideploys unchanged. It is complete except for one input the canvas cannot supply:
vmids and IPs.
Without an allocator, a UI-authored scenario will collide with
demo_lab(or any otherscenario) on a shared Proxmox. This is the last piece before a UI-authored scenario can
be deployed live.
The ledger it must allocate against
range42-playbooksscenarios/_reserved.json(ondev) is the global reservationledger: NDJSON, one entry per VM and per template, regenerated from every scenario's
manifest/scenario_vms.jsonbyscenarios/_regenerate_reserved.sh, and validated byscenarios/_check_reserved.sh(8 rules: vmid collisions,(bridge, ip)collisions,template consistency across scenarios, and cross-role collisions).
Currently 192 entries. Existing scenarios each own a contiguous vmid block —
demo_lab=11xx,blank_scenario_2=2xxx,blank_scenario_4=4xxx,blank_scenario_6=6xxx,debug_*=8xxx,_init_lab=9xx, templates=9xxx — and 13 shared9xxxtemplate rows are duplicated byte-identically into every scenario that uses them(this is what
_check_reserved.shrule 5 enforces).Requirements
Pure function.
allocate(draft, reserved_entries) -> ScenarioSpec. No git, nofilesystem, no network inside
app/core/. The caller parses the ledger and passes it in.Fully unit-testable.
Per-scenario vmid block. UI-authored scenarios allocate from a dedicated range
(proposal:
3000-3999, 100-wide blocks), taking the lowest free block. Keeps the ledgerlegible and keeps a scenario that grows by two VMs from scattering across the space.
Pin on re-render. The allocator takes the scenario's previous manifest as an optional
input and keeps every surviving
vm_name's vmid/IP byte-identical. Only genuinely newVMs get fresh allocations; removed VMs free their slots. Without this, a canvas edit
renumbers live VMs and the deployer destroys and recreates them.
Multi-NIC-native internal model. The unit of IP allocation must be
(vm, nic), not(vm). Segmented topologies (internet / DMZ / office LANs) need a firewall or router VMwith a leg in each segment, and building the allocator single-NIC means rewriting it
later. See the manifest-schema issue for what can actually be emitted today — the
internal model should be ahead of the emitted schema, not behind it.
No hardcoded address assumptions. Do not assume
192.168.xor/24. Bridge, CIDR andgateway are inputs (canvas network nodes already carry
cidr_template/bridge_template,and attachments carry
ip/ip_template). Useipaddressfrom stdlib; reserve thenetwork address, the broadcast address and the gateway. This keeps the allocator working
unchanged when networks move to SDN VNets with arbitrary addressing.
Template rows. Copy the shared
9xxxtemplate rows for the templates the scenario'sVMs actually reference, verbatim from the ledger, so
_check_reserved.shrule 5 staysgreen.
Loud failure. Raise on an exhausted block range, on a pinned vmid that a third party
has since taken, and on any intra-scenario collision. Never silently renumber.
Notes
app/core/scenario_renderer/manifest.pyalready enforces the intra-scenario half of thecollision rules (
_validate: duplicatevm_name, duplicatevm_id, duplicate(bridge, ip), VM-vs-template vmid collision). The allocator should make those unreachablerather than duplicate them.
Acceptance
manifest/scenario_vms.json, dropped intorange42-playbooks/scenarios/,passes
_check_reserved.shwith no failures.