ARD is a local development and test harness for VM-backed DevStack deployments. The primary local workflow uses Ansible provider playbooks to provision libvirt VMs, generate an ARD/Zuul-like inventory, and then run the existing DevStack deployment roles inside those VMs.
The current local provider target is libvirt. KubeVirt is planned as a provider, OpenShift-related workloads are supported, and static is now available for deployments on pre-provisioned SSH hosts.
Docs navigation was reorganized; see:
docs/repo-navigation.mdfor repo layout and entry pointsdocs/README.mdfor the documentation indexansible/README.mdfor Ansible structure and canonical playbook paths
Bootstrap the repository and host dependencies:
./bootstrap-repo.shRender a named deployment workspace:
make render ARD_DEPLOYMENT=devstack-aCreate the VMs and generated inventory:
make apply ARD_DEPLOYMENT=devstack-a
make ping ARD_DEPLOYMENT=devstack-aSSH to a deployment node, or print the SSH command without running it:
make ssh ARD_DEPLOYMENT=devstack-a ARD_NODE=controller
make ssh-print ARD_DEPLOYMENT=devstack-a ARD_NODE=compute-1
# equivalent explicit dry-run/print mode
make ssh ARD_DEPLOYMENT=devstack-a ARD_NODE=compute-1 ARD_SSH_PRINT=1Deploy DevStack:
make deploy ARD_DEPLOYMENT=devstack-a
make verify ARD_DEPLOYMENT=devstack-aDestroy provider resources when finished:
make destroy ARD_DEPLOYMENT=devstack-aRemove the local deployment workspace only when you no longer need the rendered inputs or generated state:
make cleanup ARD_DEPLOYMENT=devstack-aThe normal local workflow is:
render: create a concrete deployment workspace from presets and optional overlays.apply: create libvirt network/domain/disk/seed resources, generate inventory, and wait for SSH/cloud-init readiness.ping: verify SSH/Ansible connectivity again.deploy: run the multinode DevStack playbook.verify: run basic post-deploy checks.destroy: remove libvirt resources but keep the workspace and generated artifacts for inspection.clean-generated: remove generated inventory/state/rendered artifacts without touching provider resources.cleanup: delete the workspace.
A deployment workspace lives under:
deployments/<deployment-name>/
deployment.yaml
nodes.yaml
devstack/
common.yaml
group_vars/
controller.yaml
compute.yaml
host_vars/
inventory.yaml # generated by apply
provider-state.yaml # generated by apply
rendered/ # generated provider artifacts
logs/
render treats deployment.yaml, nodes.yaml, and devstack/*.yaml as generated output and may overwrite them. Keep custom intent in a render file or deployment-local overlay such as overrides/render.yaml.
The root Makefile wraps the provider playbooks. The default target is a full
local rebuild workflow:
make
# equivalent to
make defaultmake default runs destroy-clean-generated, cleanup, render, apply,
ping, deploy, and verify in order. The initial destroy and cleanup steps
are best-effort so the target also works from a fresh checkout.
Individual workflow targets are also available:
make render
make apply
make ping
make ssh
make ssh-print
make deploy
make verify
make destroy
make destroy-clean-generated
make clean-generated
make cleanup
make siteUseful variables:
ARD_DEPLOYMENT deployment name, default devstack-1
ARD_DEPLOYMENTS_DIR deployment parent dir, default ./deployments
ARD_DEPLOYMENT_DIR full workspace path
ARD_PROVIDER provider, currently libvirt
ARD_TOPOLOGY topology preset
ARD_TARGET_BRANCH DevStack target branch, default master
ARD_WORKLOAD workload to deploy, default devstack; use microshift for MicroShift
ARD_SERVICES comma-separated service profiles, default devstack,ovn,tempest; empty for MicroShift
ARD_PROVIDER_PROFILE provider profile, default local-libvirt
ARD_IMAGE optional image key override; defaults to centos-stream-10 for MicroShift
ARD_NETWORK_CIDR libvirt management CIDR, default 192.168.96.0/24
ARD_RENDER_FILE optional render intent file loaded before Make vars
ARD_NODE inventory node for make ssh, default controller
ARD_SSH_PRINT print SSH command without running it when set to 1
ARD_SSH_ARGS extra arguments passed to ssh
ARD_EXTRA_VARS extra Ansible vars appended to provider commands
Example:
make render \
ARD_DEPLOYMENT=devstack-a \
ARD_TARGET_BRANCH=master \
ARD_TOPOLOGY=one-controller-two-compute \
ARD_SERVICES=devstack,ovn,tempest \
ARD_NETWORK_CIDR=192.168.99.0/24Use a unique deployment name and management CIDR for each local deployment:
make render ARD_DEPLOYMENT=devstack-a ARD_NETWORK_CIDR=192.168.99.0/24
make render ARD_DEPLOYMENT=devstack-b ARD_NETWORK_CIDR=192.168.100.0/24Provider resources are named with the deployment name, for example:
ard-devstack-a-controller
ard-devstack-a-compute-1
Inventory hostnames remain logical names such as controller, compute-1, and
compute-2.
Supported local render presets:
all-in-one
one-controller-one-compute
one-controller-two-compute
all-in-one renders:
controller
one-controller-one-compute renders:
controller
compute-1
one-controller-two-compute renders:
controller
compute-1
compute-2
Topology presets are built from generic node pools. Singleton pools can set an explicit name such as controller; counted pools default to readable hyphenated names such as {type}-{index}. Multinode topologies disable nova-compute on the controller through the rendered controller group vars when the topology says the controller does not run compute services.
Render can start from a small intent file:
---
ard_provider: libvirt
ard_provider_profile: local-libvirt
ard_target_branch: stable/2026.1
ard_topology: one-controller-one-compute
ard_service_profiles:
- devstack
- ovn
- tempest
ard_libvirt_network_cidr: 192.168.98.0/24Use it with:
make render ARD_DEPLOYMENT=stable-test ARD_RENDER_FILE=examples/render.yamlUse ard_render_overrides for simple kustomize-like customizations. Overrides use ordinary recursive dictionary merge semantics: later dictionaries replace scalar and list values for the relevant section.
ard_management_network: ard-mgmt
ard_render_overrides:
provider_defaults:
image: ubuntu-24.04
node_pools:
compute:
count: 2
flavor: devstack-compute
profiles:
- ssh
- nested_virt
- performance
networks:
- name: ard-mgmt
ip_start: 3
- name: storage
ip_start: 20
networks:
storage:
cidr: 192.168.120.0/24
provider_network: ard-storage
devstack:
common:
enable_ceph: true
controller:
controller_localrc_extra:
DEBUG_LIBVIRT_COREDUMPS: true
ard_render_node_overrides:
compute-2:
image: ubuntu-24.04
flavor: devstack-compute
profiles:
- ssh
- nested_virt
- gpu
networks:
ard-mgmt:
ip: 192.168.98.50The libvirt provider supports multiple rendered networks. ard_management_network selects which attached network is used for SSH inventory and nodepool.private_ipv4; additional networks are rendered as extra libvirt networks and attached as additional VM interfaces.
The built-in tenant network preset is isolated and opt-in. It is not attached by default, but can be added to a node pool when guests need a bridge-only network for their own VLANs, DHCP, or overlay experiments:
ard_render_overrides:
node_pools:
compute:
networks:
- name: ard-mgmt
ip_start: 3
- name: tenant
mac_start: 20Isolated networks render as libvirt networks without host-side IP, NAT, or DHCP. Guest interfaces are still given deterministic MAC addresses and stable interface names.
Current service profiles are:
devstack
ovn
tempest
ceph
Reusable provider defaults live in the ARD provider common role. Deployment workspaces normally only select the image/flavor by name instead of embedding the full registry.
Current image keys:
debian-13
ubuntu-24.04
centos-stream-10
fedora-eln
almalinux-10
rocky-linux-10
Current flavor keys:
devstack-control 8 vCPU, 16 GiB RAM, 80 GiB disk
devstack-compute 8 vCPU, 8 GiB RAM, 80 GiB disk
microshift-node 8 vCPU, 8 GiB RAM, 80 GiB disk
The default local image is Debian 13 genericcloud.
Base cloud images are cached under:
$XDG_CACHE_HOME/ard/images
or, if XDG_CACHE_HOME is unset:
~/.cache/ard/images
Per-deployment libvirt disks, seed ISOs, NVRAM, and console logs live under:
$XDG_STATE_HOME/ard/libvirt/images/<deployment-name>
or, if XDG_STATE_HOME is unset:
~/.local/state/ard/libvirt/images/<deployment-name>
make destroy removes per-deployment provider resources but keeps cached base
images and generated workspace artifacts for inspection. Use
make destroy-clean-generated to destroy provider resources and then remove
inventory.yaml, provider-state.yaml, and rendered/. Use
make clean-generated to remove those generated files without touching
provider resources.
Top-level Molecule scenarios are full ARD/libvirt-backed DevStack validation
flows. They call the same provider playbooks used by Make and do not use
Vagrant. The scenario source of truth lives in molecule.yml under
provisioner.ard; Molecule platforms are intentionally omitted so topology
and node names are defined only once by ARD render presets.
Available scenarios:
default Debian 13, controller + compute-1, master
one-controller-two-compute Debian 13, controller + compute-1 + compute-2
stable-2026.1 Ubuntu 24.04, controller + compute-1, stable/2026.1
Run a full scenario test:
uv run molecule test -s defaultFor a cheaper loop:
uv run molecule create -s default
uv run ansible -i molecule/default/deployment/inventory.yaml all -m ping
uv run molecule converge -s default
uv run molecule verify -s default
uv run molecule destroy -s defaultRole-level Molecule scenarios live under ansible/roles/*/molecule and use
Podman where containers are sufficient:
make molecule-test
make molecule-role-ensure_kustomizeThe local provider uses qemu:///system. Your user normally needs libvirt/qemu
group access. If bootstrap reports missing group membership, log out and back
in or use newgrp libvirt before running provider commands.
Libvirt firmware auto-selection is used for UEFI boot with secure boot disabled. Install the OVMF/edk2 firmware package for your distribution if libvirt cannot define or start the domain.
apply creates VMs and inventory, then waits for SSH and cloud-init completion
before returning. If apply times out, inspect the serial console logs under the
libvirt deployment state directory or retry:
make apply ARD_DEPLOYMENT=devstack-aMolecule create uses the same apply playbook, so it also waits for node readiness before converging.
Use a management CIDR that does not conflict with existing host networks or other ARD deployments:
make render ARD_DEPLOYMENT=devstack-b ARD_NETWORK_CIDR=192.168.100.0/24Destroy provider resources while preserving generated artifacts for inspection:
make destroy ARD_DEPLOYMENT=devstack-aDestroy provider resources and remove generated inventory/state/rendered files:
make destroy-clean-generated ARD_DEPLOYMENT=devstack-aRemove generated files without touching provider resources:
make clean-generated ARD_DEPLOYMENT=devstack-aIf the workspace is no longer needed:
make cleanup ARD_DEPLOYMENT=devstack-aGenerated runtime files are ignored by git:
inventory.yaml
provider-state.yaml
rendered/
logs/